Perché non è possibile assegnare un metodo anonimo a var?


139

Ho il codice seguente:

Func<string, bool> comparer = delegate(string value) {
    return value != "0";
};

Tuttavia, non viene compilato quanto segue:

var comparer = delegate(string value) {
    return value != "0";
};

Perché il compilatore non riesce a capire che è un Func<string, bool> ? Accetta un parametro stringa e restituisce un valore booleano. Invece, mi dà l'errore:

Impossibile assegnare il metodo anonimo a una variabile locale tipizzata in modo implicito.

Ho una supposizione e cioè se la versione var compilata , mancherebbe di coerenza se avessi il seguente:

var comparer = delegate(string arg1, string arg2, string arg3, string arg4, string arg5) {
    return false;
};

Quanto sopra non avrebbe senso poiché Func <> consente solo fino a 4 argomenti (in .NET 3.5, che è quello che sto usando). Forse qualcuno potrebbe chiarire il problema. Grazie.


3
Nota sull'argomento 4 argomenti , in .NET 4, Func<>accetta fino a 16 argomenti.
Anthony Pegram,

Grazie per il chiarimento. Sto usando .NET 3.5.
Marlon,

9
Perché il compilatore dovrebbe pensare che sia un Func<string, bool>? A Converter<string, bool>me sembra un !
Ben Voigt,


3
a volte mi manca VB ..Dim comparer = Function(value$) value <> "0"
Slai,

Risposte:


155

Altri hanno già sottolineato che ci sono infiniti possibili tipi di delegati che potresti voler dire; cosa c'è di così speciale Funcche merita di essere il default invece di Predicateo Actiono qualsiasi altra possibilità? E, per Lambdas, perché è ovvio che l'intenzione è quella di scegliere la forma del delegato, piuttosto che la forma dell'albero delle espressioni?

Ma potremmo dire che Funcè speciale e che il tipo inferito di un lambda o un metodo anonimo è Func di qualcosa. Avremmo ancora tutti i tipi di problemi. Quali tipi desideri dedurre per i seguenti casi?

var x1 = (ref int y)=>123;

Non esiste un Func<T>tipo che accetta un ref.

var x2 = y=>123;

Non conosciamo il tipo di parametro formale, sebbene conosciamo il ritorno. (O noi? Il ritorno è int? Long? Short? Byte?)

var x3 = (int y)=>null;

Non conosciamo il tipo restituito, ma non può essere nullo. Il tipo restituito può essere qualsiasi tipo di riferimento o qualsiasi tipo di valore annullabile.

var x4 = (int y)=>{ throw new Exception(); }

Ancora una volta, non conosciamo il tipo restituito e questa volta può essere nullo.

var x5 = (int y)=> q += y;

Intende essere un'istruzione lambda che restituisce il vuoto o qualcosa che restituisce il valore assegnato a q? Entrambi sono legali; quale dovremmo scegliere?

Ora, potresti dire, beh, semplicemente non supportare nessuna di queste funzionalità. Supporta solo casi "normali" in cui è possibile elaborare i tipi. Questo non aiuta. In che modo mi semplifica la vita? Se la funzione funziona a volte e fallisce a volte, devo ancora scrivere il codice per rilevare tutte quelle situazioni di errore e dare un messaggio di errore significativo per ciascuna. Dobbiamo ancora specificare tutto quel comportamento, documentarlo, scrivere test per questo e così via. Questa è una funzione molto costosa che consente all'utente di salvare una mezza dozzina di tasti. Abbiamo modi migliori per aggiungere valore alla lingua piuttosto che passare molto tempo a scrivere casi di prova per una funzionalità che non funziona per metà del tempo e che non fornisce quasi alcun vantaggio nei casi in cui funziona.

La situazione in cui è effettivamente utile è:

var xAnon = (int y)=>new { Y = y };

perché non esiste un tipo "parlabile" per quella cosa. Ma abbiamo questo problema in ogni momento e usiamo solo l'inferenza del tipo di metodo per dedurne il tipo:

Func<A, R> WorkItOut<A, R>(Func<A, R> f) { return f; }
...
var xAnon = WorkItOut((int y)=>new { Y = y });

e ora l'inferenza del tipo di metodo determina quale sia il tipo di funzione.


43
Quando hai intenzione di compilare le tue risposte SO in un libro? Lo comprerei :)
Matt Greer,

13
Secondo la proposta di un libro di Eric Lippert con risposte SO. Titolo suggerito: "Riflessioni dallo stack"
Adam Rackis,

24
@Eric: buona risposta, ma è leggermente fuorviante illustrarlo come qualcosa che non è possibile, poiché in realtà funziona perfettamente in D. È solo che voi ragazzi non avete scelto di dare ai letterati delegati il ​​loro tipo, e invece li avete fatti dipendere nei loro contesti ... quindi IMHO la risposta dovrebbe essere "perché è così che l'abbiamo fatta" più di ogni altra cosa. :)
user541686

5
@abstractdissonance Noto anche che il compilatore è open source. Se ti interessa questa funzionalità, puoi donare il tempo e gli sforzi necessari per realizzarla. Ti incoraggio a inviare una richiesta pull.
Eric Lippert,

7
@AbstractDissonance: abbiamo misurato il costo in termini di risorse limitate: sviluppatori e tempo. Questa responsabilità non è stata concessa da Dio; è stato imposto dal vice presidente della divisione sviluppatori. L'idea che in qualche modo il team C # possa ignorare un processo di bilancio è strana. Vi assicuro che i compromessi sono stati e sono ancora fatti dalla considerazione attenta e ponderata degli esperti che hanno espresso i desideri delle comunità C #, la missione strategica per Microsoft e il loro eccellente gusto nel design.
Eric Lippert,

29

Solo Eric Lippert lo sa per certo, ma penso che sia perché la firma del tipo delegato non determina in modo univoco il tipo.

Considera il tuo esempio:

var comparer = delegate(string value) { return value != "0"; };

Ecco due possibili inferenze per ciò che vardovrebbe essere:

Predicate<string> comparer  = delegate(string value) { return value != "0"; };  // okay
Func<string, bool> comparer = delegate(string value) { return value != "0"; };  // also okay

Quale dovrebbe dedurre il compilatore? Non c'è un buon motivo per scegliere l'uno o l'altro. E sebbene a Predicate<T>sia funzionalmente equivalente a a Func<T, bool>, sono comunque tipi diversi a livello del sistema di tipi .NET. Il compilatore pertanto non può risolvere in modo inequivocabile il tipo delegato e non deve inferire l'inferenza del tipo.


1
Sono sicuro che anche altre persone in Microsoft lo sanno per certo. ;) Ma sì, alludi a una ragione principale, il tipo di tempo di compilazione non può essere determinato perché non ce n'è. La sezione 8.5.1 della specifica della lingua evidenzia in modo specifico questo motivo per non consentire l'utilizzo di funzioni anonime nelle dichiarazioni di variabili tipizzate in modo implicito.
Anthony Pegram,

3
Sì. E ancora peggio, per lambda non sappiamo nemmeno se si sta andando a un tipo delegato; potrebbe essere un albero delle espressioni.
Eric Lippert,

Per chiunque sia interessato, ho scritto un po 'di più su questo e su come gli approcci C # e F # contrastano su mindscapehq.com/blog/index.php/2011/02/23/…
itowlson,

perché il compilatore non può semplicemente fabbricare un nuovo tipo unico come fa C ++ per la sua funzione lambda
Weipeng L

In che modo differiscono "a livello del sistema di tipi .NET"?
arao6,

6

Eric Lippert ha un vecchio post a riguardo dove dice

E in effetti la specifica C # 2.0 lo chiama fuori. Le espressioni del gruppo di metodi e le espressioni di metodo anonime sono espressioni senza tipo in C # 2.0 e le espressioni lambda le uniscono in C # 3.0. Pertanto è illegale per loro apparire "nudi" sul lato destro di una dichiarazione implicita.


E questo è sottolineato dalla sezione 8.5.1 della specifica della lingua. "L'espressione dell'inizializzatore deve avere un tipo di tempo di compilazione" per poter essere utilizzata per una variabile locale tipizzata in modo implicito.
Anthony Pegram,

5

Diversi delegati sono considerati tipi diversi. ad esempio, Actionè diverso da MethodInvoker, e un'istanza di Actionnon può essere assegnata a una variabile di tipo MethodInvoker.

Quindi, dato un delegato anonimo (o lambda) come () => {}, è un Actiono un MethodInvoker? Il compilatore non può dirlo.

Allo stesso modo, se dichiaro un tipo di delegato prendendo un stringargomento e restituendo un bool, come farebbe il compilatore a sapere che volevi davvero un tipo al Func<string, bool>posto del mio delegato? Non può inferire il tipo delegato.


2

I seguenti punti provengono da MSDN per quanto riguarda le variabili locali tipizzate in modo implicito:

  1. var può essere utilizzato solo quando una variabile locale viene dichiarata e inizializzata nella stessa istruzione; la variabile non può essere inizializzata su null, su un gruppo di metodi o su una funzione anonima.
  2. La parola chiave var indica al compilatore di dedurre il tipo di variabile dall'espressione sul lato destro dell'istruzione di inizializzazione.
  3. È importante comprendere che la parola chiave var non significa "variante" e non indica che la variabile è tipizzata in modo approssimativo o in ritardo. Significa solo che il compilatore determina e assegna il tipo più appropriato.

Riferimento MSDN: variabili locali tipizzate in modo implicito

Considerando quanto segue per quanto riguarda i metodi anonimi:

  1. I metodi anonimi consentono di omettere l'elenco dei parametri.

Riferimento MSDN: metodi anonimi

Sospetto che dal momento che il metodo anonimo potrebbe effettivamente avere firme di metodi diversi, il compilatore non è in grado di inferire correttamente quale sarebbe il tipo più appropriato da assegnare.


1

Il mio post non risponde alla domanda reale, ma risponde alla domanda sottostante di:

"Come posso evitare di dover scrivere un tipo fugly come Func<string, string, int, CustomInputType, bool, ReturnType>?" [1]

Essendo il programmatore pigro / azzurrato che sono, ho sperimentato con l'uso Func<dynamic, object> - che accetta un singolo parametro di input e restituisce un oggetto.

Per più argomenti, puoi usarlo in questo modo:

dynamic myParams = new ExpandoObject();
myParams.arg0 = "whatever";
myParams.arg1 = 3;
Func<dynamic, object> y = (dynObj) =>
{
    return dynObj.arg0.ToUpper() + (dynObj.arg1 * 45); //screw type casting, amirite?
};
Console.WriteLine(y(myParams));

Suggerimento: è possibile utilizzare Action<dynamic> se non è necessario restituire un oggetto.

Sì, lo so che probabilmente va contro i tuoi principi di programmazione, ma questo ha senso per me e probabilmente per alcuni programmatori Python.

Sono piuttosto alle prime armi con i delegati ... volevo solo condividere ciò che ho imparato.


[1] Ciò presuppone che tu non stia chiamando un metodo che richiede un predefinito Funccome parametro, nel qual caso dovrai digitare quella stringa fugly: /


0

Che ne dici?

var item = new
    {
        toolisn = 100,
        LangId = "ENG",
        toolPath = (Func<int, string, string>) delegate(int toolisn, string LangId)
        {
              var path = "/Content/Tool_" + toolisn + "_" + LangId + "/story.html";
              return File.Exists(Server.MapPath(path)) ? "<a style=\"vertical-align:super\" href=\"" + path + "\" target=\"_blank\">execute example</a> " : "";
        }
};

string result = item.toolPath(item.toolisn, item.LangId);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.