2014-10-21 8 views
9

Okay, lassen Sie mich die Szene setzen: Wir haben eine Funktion in unserem Code verwendet, die eine Funktion und macht einige Logging um es herum und gibt dann das Ergebnis zurück . Es sieht ein bisschen so aus.Für Func <T, TResult>, wo A verlängert T, A erfüllt nicht für T

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

Im Gebrauch mit dieser habe ich die vier folgenden Objekte

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 { 
     ... 
    } 
} 

Also, mit diesem I Mock LoggedApiCall (mit Moq), um einige Unit-Tests zu unterstützen versuchen. Ich schreibe eine generische Methode, die es uns ermöglicht, eine Funktion übergeben, die die Basistyp-Einschränkungen erfüllt, und eine Antwort, die auch Übereinstimmungen eingibt, um eine allgemeine Methode zu erstellen, um .Setup() auf dem Mock auszuführen.

Es sieht wie folgt aus:

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); 
    } 
} 

Der Grund, warum ich die Funktion zu werfen versucht bin, ist, dass, wenn ich nicht tun, erhalte ich die Intellisense Fehler

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

Dieses ich etwas finden das verwirrend ist, da TRequest und TResponse wie durch BaseRequest und BaseResponse jeweils eingeschränkt werden, aber wenn ich umgehen muss, werde ich. Wenn jedoch die Besetzung

var baseFunction = function as Func<BaseRequest, BaseResponse> 

Durchführung löst es als null. Dies finde ich auch verwirrend aufgrund der oben erwähnten Einschränkungen für den Parameter, der an SetupLoggedApiCall übergeben wurde. Ich habe einige graben weiter, während das Debuggen des Codes und bekam die folgende:

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

Wie diese zeigt, setzt tAntwort BaseResponse gerecht zu werden und kann ohne Probleme auf sie geworfen werden. Sobald wir jedoch versuchen, von TRequest zu BaseRequest zu wechseln, schlägt es fehl. Nur um sicherzustellen, dass ich nicht in eine Situation bekommen, wo keine falschen Arten importiert oder irgendetwas folgte ich diese uns mit:

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

Also, kann mir jemand sagen: Da alles auf TRequest eine BaseRequest da dies Cast scheitert an der Frage von TRequest?

Wir werden anfangen, dies zu entfernen und das Problem in einem neuen Code-Projekt zu isolieren (in Wirklichkeit ist unser Code nicht ganz so einfach wie unten, ich habe ihn vereinfacht) und sehe, an welchem ​​Punkt es scheitert und wir werden aktualisieren, wenn wir etwas finden, aber jede Einsicht würde geschätzt werden.

Update 1

Nach dem Vorschlag folgenden @EugenePodskal ich die Definition von LoggedApiCall aktualisiert zu lesen

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

Das machte SetupLoggedApiCall glücklich, zumindest zum Zeitpunkt der Kompilierung, in dem, was sein war Eingereicht würde jedoch gültig sein, der Aufruf an den verspotteten Dienst gibt immer noch null zurück. Ich grub wieder in das Proxy-Objekt und bis auf das Interceptor für diesen Anruf und ich entdecken diese:

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

dass kein Tippfehler ist. Der erste Parameter fehlt einfach im Interzeptor.Ich schätze, das verlagert die Frage mehr auf Mock als auf Func, aber wenn man sieht, dass der Interceptor ein Lamba-Ausdruck ist, kann jeder etwas erhellen, was dazu führen könnte, dass dieser Parameter einfach fehlt.

+1

Ich sehe nicht, wie dies ein Duplikat ist - die verknüpfte Frage ist eine Erläuterung einiger Details bezüglich der Varianz von 'Func', während diese Frage tatsächlich eine Einführung/Erklärung der Varianz im Allgemeinen erfordert. Der Kontext ist der gleiche, aber die Frage ist nicht. Die Antwort auf die andere Frage wird dem Fragesteller wahrscheinlich nicht helfen. –

+0

@AntP Die Frage verbunden ist, "warum kann ich nicht" Aktion 'zu' Aktion '", und diese Frage im Wesentlichen fragen "Warum kann ich nicht' Func '' 'Func ' ". – Rawling

+1

@Rawling Sicher, wenn Sie Dinge vereinfachen möchten. Die andere Frage und ihre Antworten nehmen bereits das Verständnis des Begriffs der Varianz an, so dass die Antworten auf diese Frage nutzlos sind, wenn sie auf diesen angewendet werden. Z.B. "Warum kann ich nicht' Func '' 'Func ' 'kann nicht richtig mit" Sie haben die Kovarianz und Kontravarianz den falschen Weg. " –

Antwort

1

Das Thema, das Sie brauchen, zu betrachten ist Covariance and Contravariance in Generics

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

heißt "In .NET Framework 4, die Func generische Delegaten, wie Func, kovarianten Rückgabetypen und kontra Parametertypen haben."

Es ist logisch, als auch, wenn man darüber nachdenkt ..

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); 

daher für die Funktion Parameter, Sie wollen nicht cast Basistyp zu ermöglichen, nach oben.

Auf der anderen Seite, für Rückkehr Werte, wenn die Funktion ursprünglich zurückzukehren Instanz AppleProduct erklärt wurde, ist es 100% (Typ) sicher zu sagen, dass es eine Instanz von FuitProduct (Basisklasse für AppleProduct) gibt