2014-10-21 14 views
9

Va bene, mi permetta di impostare la scena: Abbiamo una funzione utilizzata all'interno del nostro codice che prende una funzione e fa qualche registrazione intorno ad esso e poi restituisce il risultato . Sembra un po 'qualcosa di simile.Per Func <T, TResult>, dove A estende T, A non soddisfa per T

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

In uso con questo ho le seguenti quattro oggetti

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

Così, con questi che sto cercando LoggedApiCall finto (utilizzando Moq) al fine di supportare alcune unit test. Sto scrivendo un metodo generico che ci consente di passare una funzione che soddisfa i vincoli del tipo di base e una risposta che digita anche le corrispondenze per creare un metodo comune per eseguire .Setup() sul Mock.

Ecco come si presenta:

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

Il motivo per cui sto cercando di lanciare la funzione è che se non lo faccio, ottengo l'errore intellisense

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

Questo, trovo un po ' bemusing come TRequest e TResponse sono vincolati come da BaseRequest e BaseResponse rispettivamente, ma se lavoro intorno devo, lo farò. Tuttavia, quando si esegue il cast

var baseFunction = function as Func<BaseRequest, BaseResponse> 

si risolve come nullo. Questo, trovo anche bemusing a causa dei vincoli sopra menzionati sul parametro passato in SetupLoggedApiCall. Ho fatto qualche ulteriore scavo, mentre il debug del codice e ha ottenuto la seguente:

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

Come illustra la presente, tRisposta continua a soddisfare BaseResponse e può essere gettato ad esso senza problemi. Tuttavia, non appena proviamo a passare da TRequest a BaseRequest, fallisce. Giusto per assicurarsi che io non ero sempre in una situazione in cui importato alcun tipo sbagliato o qualcosa Ho seguito questo noi con:

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

Così, qualcuno può dirmi: Dato che tutto fa pensare TRequest essere un BaseRequest questo il cast fallisce in merito a TREGEST?

Stiamo per iniziare a rimuovere questo problema e isolare il problema in un nuovo progetto di codice (in realtà, il nostro codice non è così semplice come lo è in basso, l'ho semplificato al suo nucleo) e vediamo a quale punto fallisce e aggiorneremo se troviamo qualsiasi cosa, ma qualsiasi intuizione sarebbe apprezzata.

Update 1

Al seguito dalla suggestione @EugenePodskal Ho aggiornato la definizione di LoggedApiCall leggere

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

Questo fatto SetupLoggedApiCall felice, almeno al momento della compilazione, in quanto ciò che è stato passato in sarebbe valido tuttavia, la chiamata al servizio deriso restituisce ancora null. Ho scavato nuovamente dentro l'oggetto proxy e giù per l'intercettore per questa chiamata e ho scoperto questo:

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

Questo non è un errore di battitura. Il primo parametro semplicemente manca dall'intercettore.Immagino che questo cambi la domanda di essere più su Mock che su Func, ma visto che l'intercettatore è un'espressione di lamba chiunque sia in grado di far luce su ciò che potrebbe causare che il paramater semplicemente manchi?

+1

Non vedo come questo sia un duplicato - la domanda collegata è un chiarimento di alcuni dettagli riguardanti la varianza di 'Func' mentre questa domanda richiede in realtà un'introduzione/spiegazione della varianza in generale. Il contesto è lo stesso, ma la domanda non lo è. La risposta all'altra domanda probabilmente non aiuterà il richiedente di questo. –

+0

@AntP La domanda collegata è "perché non posso lanciare l'azione ' nell'azione '", e questa domanda è essenzialmente chiedendo "perché non posso trasmettere' Func 'a Func ' ". – Rawling

+1

@Rawling Certo, se ti piace semplificare eccessivamente le cose. L'altra domanda e le sue risposte presuppongono già la comprensione del concetto di varianza, quindi le risposte a questa domanda sono inutili se applicate a questo. Per esempio. "Perché non posso trasmettere' Func 'a' Func '" non si può rispondere correttamente con "Hai la covarianza e la controvarianza nel modo sbagliato". –

risposta

1

L'argomento che dovete guardare è Covariance and Contravariance in Generics

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

vale a dire "In .NET Framework 4, i delegati generici Func, come Func, hanno tipi restituiti covarianti e tipi di parametri controvarianti."

E 'logico così, se ci pensate ..

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

Quindi, per funzione parametri, non si desidera consentire fino cast tipo di base.

D'altra parte, per valori di ritorno, se la funzione è stato originariamente dichiarato di restituire un'istanza di AppleProduct, è al 100% (tipo) sicuro di dire che restituisce un'istanza di FuitProduct (classe base per AppleProduct)

Problemi correlati