2012-02-23 42 views
11

So che quello che sto facendo può essere fatto in un modo diverso, ma sono curioso di sapere come funzionano le cose. Quello che segue è un codice semplificato che non viene compilato, ma che dovrebbe mostrare il mio obiettivo.Passare una funzione generica come parametro

private void Execute() 
{ 
    GeneralizedFunction("1", "2", i => Transform(i)); 
} 

void GeneralizedFunction(string aStringA, string aStringB, Func<string, T> aAction) 
{ 
    A result1 = aAction(aStringA); 
    B result2 = aAction(aStringB); 
    // Do something with A and B here 
} 

T Transform<T>(string aString) 
{ 
    return default(T); 
} 

Transform è una conversione generico da stringa ad un oggetto (si pensi deserializzazione). GeneralizedFunction utilizza due specializzazioni di trasformazione: una per il tipo A e una per il tipo B. So che posso farlo in molti altri modi (ad esempio introducendo un parametro per il tipo di oggetto), ma sto cercando spiegazioni se è possibile o impossibile farlo con generici/lambda. Se Transform è specializzato prima di essere passato come parametro a GeneralizedFunction, allora è impossibile. Quindi la domanda è perché questa possibilità è limitata.

+0

cosa si tratta vuoi fare esattamente? dal momento che non vuoi dare a GeneralizedFunction alcun tipo di informazione riguardante la funzione Transform, perché non accettare una funzione di nuovo prendendo una stringa e restituendo un oggetto (di cui tutti sanno che tutti sono *) – Polity

+0

Il fatto è, il tuo "Fai qualcosa con Il segnaposto A e B "nasconde la parte problematica. A e B saranno sempre tipi particolari? Quindi non hai bisogno di farmaci generici. Sono tipi arbitrari (magari con vincoli)? Quindi 'GeneralizedFunction' deve essere generico in loro. – AakashM

+0

A e B sono tipi concreti, ma Trasforma è una funzione generica. – Max

risposta

1

provare la seguente firma:

void GeneralizedFunction<T>(string aStringA, string aStringB, Func<string, T> aAction) 

(Si noti che GeneralizedFunction deve essere generico, il compilatore indovinare automaticamente il parametro type quando si chiama il metodo).

+0

L'ho provato. Il problema è che stai cercando di riferirti a entrambi i tipi A e B con T qui. Era mia intenzione rimuovere dalla dichiarazione di funzione. – Max

+0

Quindi dovrai sostituire sia A che B con T. – Matthias

+0

Ho bisogno di eseguire operazioni specifiche sugli oggetti all'interno di GeneralizedFunction per semplicità. Non ho scritto alcun dettaglio nel mio codice di esempio, ma tutto ciò che è scritto è necessario. – Max

0
void GeneralizedFunction<T>(string aStringA, string aStringB, Func<string, T> aAction) 
{ 
    A result1 = aAction(aStringA); 
    B result2 = aAction(aStringB); 
} 

T Transform<T>(string aString) 
{ 
    return default(T); 
} 
4

Quello che stai chiedendo di fare non è possibile usando i generici da solo. Il compilatore deve generare due versioni digitate della tua funzione Transform: una per restituire il tipo A e una per il tipo B. Il compilatore non ha modo di saperlo generare in fase di compilazione; solo eseguendo il codice saprebbe che A e B sono obbligatori.

Un modo per risolverlo sarebbe quello di passare nelle due versioni:

private void Execute() 
{ 
    GeneralizedFunction("1", "2", i => Transform<A>(i), i => Transform<B>(i)); 
} 

void GeneralizedFunction(string aStringA, string aStringB, Func<string, A> aAction, Func<string, B> bAction) 
{ 
    A result1 = aAction(aStringA); 
    B result2 = bAction(aStringB); 
} 

il compilatore sa esattamente quello di cui ha bisogno per generare in questo caso.

+0

Sì. So che potrei passare due istanze, ma speravo che fosse possibile passare una funzione GENERICA (da cui il titolo della mia domanda) e creare due specializzazioni di quella funzione generica all'interno di GeneralizedFunction. – Max

+0

So che il codice non è quello che vuoi. La tua domanda era perché la possibilità è limitata. Spero che tu capisca ora perché quello che vuoi fare non funzionerà con il compilatore. –

+0

Se guardo il codice IL generato per la funzione Transform, sembra che ci sia solo una versione di esso. Anche quando lo applico a due classi di oggetti. Quindi sembra che la specializzazione di una funzione generica sia fatta in fase di runtime. Non è vero? – Max

1

Sembra che la risposta sia "no".

Quando si chiama Transform direttamente, è necessario specificare un parametro di tipo:

int i = Transform<int>(""); 

Quindi ipoteticamente, se si poteva passare una funzione generica incompleto-costruito come si vuole, avresti bisogno di specificare il parametri di tipo così:

void GeneralizedFunction(string aStringA, string aStringB, Func<string, T> aAction) 
{ 
    A result1 = aAction<A>(aStringA); 
    B result2 = aAction<B>(aStringB); 
    // Do something with A and B here 
} 

Quindi mi sembra che si potrebbe ipoteticamente fare questo, se C# ha avuto una sintassi del genere.

Ma qual è il caso d'uso? A parte trasformare le stringhe al valore predefinito di un tipo arbitrario, non vedo molto l'uso per questo. Come potresti definire una funzione che fornisca un risultato significativo in uno dei due diversi tipi utilizzando la stessa serie di affermazioni?

EDIT

L'analisi del motivo per cui non è possibile:

Quando si utilizza un'espressione lambda nel codice, viene compilato in sia un delegato o di un albero di espressione; in questo caso, è un delegato. Non puoi avere un'istanza di tipo generico "aperto"; in altre parole, per creare un oggetto da un tipo generico, è necessario specificare tutti i parametri del tipo.In altre parole, non c'è modo di avere un'istanza di un delegato senza fornire argomenti per tutti i suoi parametri di tipo.

Una delle funzioni utili del compilatore C# è rappresentata dalle conversioni di gruppi di metodi impliciti, in cui il nome di un metodo (un "gruppo di metodi") può essere convertito implicitamente in un tipo delegato che rappresenta uno degli overload di tale metodo. Allo stesso modo, il compilatore converte implicitamente un'espressione lambda in un tipo delegato. In entrambi i casi, il compilatore emette il codice per creare un'istanza del tipo delegato (in questo caso, per passarlo alla funzione). Ma l'istanza di quel tipo delegato deve ancora avere un argomento di tipo per ciascuno dei suoi parametri di tipo.

per passare la funzione generica come funzione generica, sembra, il compilatore avrebbe bisogno di essere in grado di passare gruppo metodo o l'espressione lambda al metodo senza conversione, così il aAction parametro avrebbe in qualche modo un tipo di "metodo gruppo" o "espressione lambda". Quindi, la conversione implicita a un tipo di delegato potrebbe avvenire nei siti di chiamata A result1 = aAction<A>(aStringA); e B result2 = aAction<B>(aStringB);. Naturalmente, a questo punto, siamo ben lungi dall'universo dei contrafactuals e degli ipotetici.

La soluzione mi è venuta durante il pranzo è stato questo, assumendo una funzione Deserialize<T> che prende una stringa contenente dati serializzati e restituisce un oggetto di tipo T:

void GeneralizedFunction<T>(string aStringA, string aStringB, Func<T, string> stringGetter) 
{ 
    A result1 = Deserialize<A>(stringGetter(aStringA)); 
    B result2 = Deserialize<B>(stringGetter(aStringB)); 
} 

void Example(string serializedA, string serializedB, string pathToA, string pathToB, FileInfo a, FileInfo b) 
{ 
    GeneralizedFunction(serializedA, serializedB, s => s); 
    GeneralizedFunction(pathToA, pathToB, File.ReadAllText); 
    GeneralizedFunction(a, b, fi => File.ReadAllText(fi.FullName)); 
} 
+0

Un caso d'uso è deserializzazione. La stringa è una rappresentazione di un oggetto e Transform crea un'istanza di quell'oggetto dalla sua rappresentazione di stringa – Max

+0

@Max Ma qual è il caso d'uso per passare 'Trasforma' a' GeneralizedFunction' piuttosto che chiamarlo direttamente? Ad ogni modo, non sarebbe un generico 'T Transform (stringa)' essere solo un metodo di convenienza intorno all'oggetto Deserialize (type, string) 'come' T Transform (string s) {return (T) Deserialize (typeof (T) , S); } ' – phoog

+0

Ad esempio Transform1 potrebbe convertire una stringa in un oggetto, Transform2 potrebbe convertire un file che una stringa punta a un oggetto – Max

4

Questa risposta non spiega il motivo perché, solo come per aggirare la limitazione.

Invece di passare una funzione di vero e proprio, è possibile passare un oggetto che ha una tale funzione:

interface IGenericFunc 
{ 
    TResult Call<TArg,TResult>(TArg arg); 
} 

// ... in some class: 

void Test(IGenericFunc genericFunc) 
{ 
    // for example's sake only: 
    int x = genericFunc.Call<String, int>("string"); 
    object y = genericFunc.Call<double, object>(2.3); 
} 

Per il vostro caso specifico utilizzo, può essere semplificata per:

interface IDeserializerFunc 
{ 
    T Call<T>(string arg); 
} 

// ... in some class: 
void Test(IDeserializerFunc deserializer) 
{ 
    int x = deserializer.Call<int>("3"); 
    double y = deserializer.Call<double>("3.2"); 
} 
Problemi correlati