2014-10-21 18 views
9

Được rồi, hãy để tôi đặt cảnh: Chúng tôi có một hàm được sử dụng trong mã của chúng tôi có chức năng và thực hiện một số thao tác đăng nhập và sau đó trả về kết quả . Nó trông giống như thế này.Đối với Func <T, TResult>, trong đó A mở rộng T, A không thỏa mãn đối với T

TResponse LoggedApiCall<TResponse>(Func<BaseRequest, BaseResponse> apiCall, ...) 
    where TResponse : BaseResponse; 

Trong sử dụng với điều này tôi có bốn đối tượng sau

namespace Name.Space.Base { 
    public class BaseRequest { 
     ... 
    } 
} 

namespace Name.Space.Base { 
    public class BaseResponse { 
     ... 
    } 
} 

namespace Some.Other.Name.Space { 
    public class Request : BaseRequest { 
     ... 
    } 
} 

namespace Name.Space { 
    public class Response<TPayload> : BaseResponse { 
     ... 
    } 
} 

Như vậy, với những tôi đang cố gắng LoggedApiCall giả (sử dụng Moq) để hỗ trợ một số đơn vị xét nghiệm. Tôi đang viết một phương thức chung cho phép chúng ta truyền vào một hàm đáp ứng các ràng buộc kiểu cơ sở và một đáp ứng cũng có thể so khớp để tạo ra một phương thức chung để thực hiện .Setup() trên Mock.

Nó trông như thế này:

protected IReturnsResult<IService> SetupLoggedApiCall<TRequest, TResponse>(
    Func<TRequest, TResponse> function, 
    TResponse response 
    ) 
    where TRequest : BaseRequest 
    where TResponse : BaseResponse 
{ 
    var baseFunction = function as Func<BaseRequest, BaseResponse>; 
    return _mockService.Setup(service => service.LoggedApiCall<TResponse>(
      baseFunction, /*other parameters * 
     )) 
     .Returns(response); 
    } 
} 

Lý do tôi đang cố gắng để cast chức năng là nếu tôi không, tôi nhận được lỗi IntelliSense

Argument type 'System.Func<TRequest, TResponse>' is not assignable to 
parameter type 'System.Func<Name.Space.Base.BaseRequest, Name.Space.Base.BaseResponse>' 

này, tôi tìm thấy một chút bemusing như TRequest và TResponse bị ràng buộc bởi BaseRequest và BaseResponse tương ứng nhưng nếu làm việc xung quanh tôi phải, tôi sẽ. Tuy nhiên, khi thực hiện dàn diễn viên

var baseFunction = function as Func<BaseRequest, BaseResponse> 

nó sẽ giải quyết là rỗng. Điều này, tôi cũng thấy bemusing do những hạn chế được đề cập trên tham số được truyền vào SetupLoggedApiCall. tôi đã làm một số tiếp tục đào trong khi gỡ lỗi mã và đã nhận như sau:

function is Func<TRequest, TResponse>  | true 
function is Func<TRequest, BaseResponse> | true 
function is Func<BaseRequest, BaseResponse> | false 

Như chương trình này, TResponse tiếp tục đáp ứng BaseResponse và có thể được đúc để nó không có vấn đề. Tuy nhiên, ngay sau khi chúng tôi cố gắng và di chuyển từ TRequest đến BaseRequest nó không thành công. Chỉ cần chắc chắn rằng tôi đã không nhận được vào một tình huống mà nhập khẩu bất kỳ loại sai hoặc bất cứ điều gì tôi sau này chúng tôi:

typeof(TRequest).BaseType == typeof(BaseRequest) | true 

Vì vậy, bất cứ ai có thể cho tôi biết: Cho rằng điểm tất cả mọi thứ để trở thành một TRequest BaseRequest này cast thất bại về vấn đề TRequest?

Chúng ta sẽ bắt đầu loại bỏ điều này và cô lập vấn đề trong một dự án mã mới (trong thực tế, mã của chúng tôi không đơn giản như dưới đây, tôi đơn giản hóa nó thành cốt lõi) và xem tại điểm nào nó không thành công và chúng tôi sẽ cập nhật nếu chúng tôi tìm thấy bất kỳ điều gì nhưng mọi thông tin chi tiết sẽ được đánh giá cao.

Cập nhật 1

Khi sau từ gợi ý @EugenePodskal tôi cập nhật định nghĩa của LoggedApiCall để đọc

TResponse LoggedApiCall<TRequest, TResponse>(Func<TRequest, TResponse> apiCall, ...) 
    where TRequest : BaseRequest where TResponse : BaseResponse 

Điều này làm SetupLoggedApiCall hạnh phúc, ít nhất là ở thời gian biên dịch, trong đó những gì đã được được thông qua sẽ là hợp lệ tuy nhiên, cuộc gọi đến dịch vụ được mô phỏng vẫn trả về giá trị rỗng. Tôi đào trở lại đối tượng proxy và xuống đến kẻ đánh chặn cho cuộc gọi này và tôi phát hiện ra điều này:

IService service => service.LoggedApiCall<Request, Response>(, /*other params*/) 

Đó không phải lỗi đánh máy. Tham số đầu tiên chỉ đơn giản là thiếu từ interceptor.Tôi đoán điều này thay đổi câu hỏi nhiều hơn là về Mock hơn Func nhưng thấy rằng kẻ đánh chặn là một biểu hiện lamba, bất cứ ai có thể làm sáng tỏ những gì có thể khiến nhân vật đó bị mất tích?

+1

Tôi không thấy làm thế nào đây là một bản sao - câu hỏi được liên kết là làm rõ một số chi tiết liên quan đến phương sai 'Func' trong khi câu hỏi này thực sự kêu gọi giới thiệu/giải thích phương sai nói chung. Bối cảnh là như nhau, nhưng câu hỏi là không. Câu trả lời cho câu hỏi khác có thể sẽ không giúp người hỏi câu hỏi này. –

+0

@AntP Câu hỏi liên quan là "tại sao tôi không thể cast 'hành động' cho' Action '", và câu hỏi này chủ yếu được hỏi "tại sao tôi không thể bầu 'Func ' cho' Func '". – Rawling

+1

@Rawling Chắc chắn, nếu bạn muốn đơn giản hóa mọi thứ. Câu hỏi khác và câu trả lời của nó đã giả định hiểu khái niệm về phương sai, vì vậy câu trả lời cho câu hỏi đó là vô ích khi áp dụng cho câu hỏi này. Ví dụ. "Tại sao tôi không thể bỏ' Func 'thành' Func '" không thể trả lời đúng với "Bạn đã có hiệp phương sai và đối nghịch với vòng sai." –

Trả lời

1

Các chủ đề bạn cần phải nhìn vào là Covariance and Contravariance in Generics

http://msdn.microsoft.com/en-us/library/dd799517(v=vs.110).aspx

ví dụ: "Trong .NET Framework 4, Func đại biểu chung chung, chẳng hạn như Func, có kiểu trả về hiệp biến và các loại tham số contravariant."

Đó là logic là tốt, nếu bạn nghĩ về nó ..

Func<Apple, AppleProduct> MakeAppleProduct = new Func....; 

// Assume the cast is allowed at runtime and doesn't throw. 
Func<Fruit, FruitProduct> MakeFruitProduct = (Func<Fruit, FruitProduct>) MakeAppleProduct; 

//Returns an instance of AppleProduct 
MakeFruitProduct(appleInstance); 

//Orange is also a Fruit, and hence we are allowed to pass it? Should it be allowed? 
MakeFruitProduct(orangeInstance); 

Do đó cho chức năng thông số, bạn không muốn cho phép lên dàn diễn viên để kiểu cơ sở.

Mặt khác, đối với trở lại giá trị, nếu chức năng ban đầu được công bố trở lại trường hợp của AppleProduct, nó là 100% (loại) an toàn để nói nó sẽ trả về một thể hiện của FuitProduct (lớp cơ sở cho AppleProduct)

Các vấn đề liên quan