Warum kann var keine anonyme Methode zugewiesen werden?

139

Ich habe folgenden Code:

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

Folgendes wird jedoch nicht kompiliert:

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

Warum kann der Compiler nicht herausfinden, dass es sich um eine handelt Func<string, bool>? Es benötigt einen String-Parameter und gibt einen Booleschen Wert zurück. Stattdessen gibt es mir den Fehler:

Einer implizit typisierten lokalen Variablen kann keine anonyme Methode zugewiesen werden.

Ich habe eine Vermutung und das heißt, wenn die var-Version kompiliert würde , würde es an Konsistenz mangeln , wenn ich Folgendes hätte:

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

Das Obige wäre nicht sinnvoll, da Func <> nur bis zu 4 Argumente zulässt (in .NET 3.5, das verwende ich). Vielleicht könnte jemand das Problem klären. Vielen Dank.

Marlon
quelle
3
Hinweis zu Ihrem Argument mit 4 Argumenten in .NET 4 Func<>akzeptiert bis zu 16 Argumente.
Anthony Pegram
Danke für die Klarstellung. Ich verwende .NET 3.5.
Marlon
9
Warum sollte der Compiler denken, dass dies ein ist Func<string, bool>? Es sieht aus wie ein Converter<string, bool>für mich!
Ben Voigt
3
manchmal vermisse ich VB ..Dim comparer = Function(value$) value <> "0"
Slai

Antworten:

155

Andere haben bereits darauf hingewiesen , dass es unendlich viele mögliche Delegattypen , dass Sie könnte gemeint haben; Was ist das Besondere daran, Funcdass es verdient, anstelle Predicateoder Actionoder einer anderen Möglichkeit die Standardeinstellung zu sein ? Und warum ist es für Lambdas offensichtlich, dass die Absicht besteht, die Delegatenform anstelle der Ausdrucksbaumform zu wählen?

Aber wir könnten sagen, dass dies etwas FuncBesonderes ist und dass der abgeleitete Typ eines Lambda oder einer anonymen Methode Func von etwas ist. Wir hätten immer noch alle möglichen Probleme. Welche Typen möchten Sie für die folgenden Fälle ableiten?

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

Es gibt keinen Func<T>Typ, der eine Referenz benötigt.

var x2 = y=>123;

Wir kennen den Typ des formalen Parameters nicht, obwohl wir die Rückgabe kennen. (Oder tun wir das? Ist die Rückgabe int? Long? Short? Byte?)

var x3 = (int y)=>null;

Wir kennen den Rückgabetyp nicht, aber er kann nicht ungültig sein. Der Rückgabetyp kann ein beliebiger Referenztyp oder ein nullbarer Werttyp sein.

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

Auch hier kennen wir den Rückgabetyp nicht und diesmal kann er ungültig sein.

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

Soll das eine nicht rückgebende Anweisung Lambda sein oder etwas, das den Wert zurückgibt, der q zugewiesen wurde? Beide sind legal; welches sollen wir wählen?

Nun, Sie könnten sagen, unterstützen Sie einfach keine dieser Funktionen. Unterstützen Sie einfach "normale" Fälle, in denen die Typen ausgearbeitet werden können. Das hilft nicht. Wie erleichtert mir das das Leben? Wenn die Funktion manchmal funktioniert und manchmal fehlschlägt, muss ich immer noch den Code schreiben, um all diese Fehlersituationen zu erkennen und für jede eine aussagekräftige Fehlermeldung zu geben. Wir müssen immer noch all dieses Verhalten spezifizieren, es dokumentieren, Tests dafür schreiben und so weiter. Dies ist eine sehr teure Funktion , die dem Benutzer ein halbes Dutzend Tastenanschläge erspart. Wir haben bessere Möglichkeiten, der Sprache einen Mehrwert zu verleihen, als viel Zeit damit zu verbringen, Testfälle für eine Funktion zu schreiben, die die Hälfte der Zeit nicht funktioniert und in Fällen, in denen sie funktioniert, kaum Vorteile bringt.

Die Situation, in der es tatsächlich nützlich ist, ist:

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

weil es für dieses Ding keinen "sprechbaren" Typ gibt. Wir haben dieses Problem jedoch ständig und verwenden nur die Inferenz des Methodentyps, um den Typ abzuleiten:

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

und jetzt ermittelt die Inferenz des Methodentyps, was der Funktionstyp ist.

Eric Lippert
quelle
43
Wann werden Sie Ihre SO-Antworten in einem Buch zusammenstellen? Ich würde es kaufen :)
Matt Greer
13
Ich stimme dem Vorschlag für ein Eric Lippert-Buch mit SO-Antworten zu. Vorgeschlagener Titel: "Reflections From The Stack"
Adam Rackis
24
@Eric: Gute Antwort, aber es ist etwas irreführend, dies als etwas zu veranschaulichen, das nicht möglich ist, da dies in D tatsächlich völlig gut funktioniert. Es ist nur so, dass ihr euch nicht dafür entschieden habt, Delegiertenliteralen ihren eigenen Typ zu geben, sondern sie davon abhängig zu machen auf ihre Kontexte ... also sollte IMHO die Antwort sein "weil wir es so gemacht haben" mehr als alles andere. :)
user541686
5
@abstractdissonance Ich stelle auch fest, dass der Compiler Open Source ist. Wenn Sie sich für diese Funktion interessieren, können Sie die erforderliche Zeit und Mühe spenden, um dies zu erreichen. Ich ermutige Sie, eine Pull-Anfrage einzureichen.
Eric Lippert
7
@AbstractDissonance: Wir haben die Kosten anhand der begrenzten Ressourcen gemessen: Entwickler und Zeit. Diese Verantwortung wurde nicht von Gott gewährt; es wurde vom Vizepräsidenten der Entwicklerabteilung auferlegt. Die Vorstellung, dass das C # -Team einen Budgetprozess irgendwie ignorieren könnte, ist seltsam. Ich versichere Ihnen, Kompromisse wurden und werden durch die sorgfältige und nachdenkliche Überlegung von Experten geschlossen, die von den C # -Communitys Wünsche, die strategische Mission für Microsoft und ihren eigenen exzellenten Geschmack im Design geäußert hatten.
Eric Lippert
29

Nur Eric Lippert weiß es genau, aber ich denke, das liegt daran, dass die Signatur des Delegatentyps den Typ nicht eindeutig bestimmt.

Betrachten Sie Ihr Beispiel:

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

Hier sind zwei mögliche Schlussfolgerungen für das, was varsein sollte:

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

Welches sollte der Compiler ableiten? Es gibt keinen guten Grund, sich für den einen oder anderen zu entscheiden. Und obwohl a Predicate<T>funktional äquivalent zu a ist Func<T, bool>, handelt es sich auf der Ebene des .NET-Typsystems immer noch um unterschiedliche Typen. Der Compiler kann daher den Delegatentyp nicht eindeutig auflösen und muss die Typinferenz nicht bestehen.

itowlson
quelle
1
Ich bin mir sicher, dass auch einige andere Leute bei Microsoft es genau wissen. ;) Aber ja, Sie spielen auf einen Hauptgrund an, der Typ der Kompilierungszeit kann nicht bestimmt werden, weil es keinen gibt. In Abschnitt 8.5.1 der Sprachspezifikation wird dieser Grund speziell hervorgehoben, warum anonyme Funktionen nicht in implizit typisierten Variablendeklarationen verwendet werden dürfen.
Anthony Pegram
3
Ja. Und noch schlimmer, für Lambdas wissen wir nicht einmal, ob es sich um einen Delegiertentyp handelt. Es könnte ein Ausdrucksbaum sein.
Eric Lippert
Für alle Interessierten habe ich unter mindscapehq.com/blog/index.php/2011/02/23/…
itowlson
Warum kann der Compiler nicht einfach einen neuen einzigartigen Typ herstellen, wie es C ++ für seine Lambda-Funktion
tut
Wie unterscheiden sie sich "auf der Ebene des .NET-Typsystems"?
arao6
6

Eric Lippert hat einen alten Beitrag darüber, wo er sagt

Tatsächlich wird dies in der C # 2.0-Spezifikation hervorgehoben. Methodengruppenausdrücke und anonyme Methodenausdrücke sind typenlose Ausdrücke in C # 2.0, und Lambda-Ausdrücke verbinden sie in C # 3.0. Daher ist es illegal, dass sie auf der rechten Seite einer impliziten Erklärung "nackt" erscheinen.

Brian Rasmussen
quelle
Dies wird in Abschnitt 8.5.1 der Sprachspezifikation unterstrichen. "Der Initialisiererausdruck muss einen Typ zur Kompilierungszeit haben", um für eine implizit typisierte lokale Variable verwendet zu werden.
Anthony Pegram
5

Unterschiedliche Delegierte werden als unterschiedliche Typen betrachtet. z. B. Actionist anders als MethodInvokerund eine Instanz von Actionkann keiner Variablen vom Typ zugewiesen werden MethodInvoker.

Also, wenn man einen anonymen Delegierten (oder Lambda) mag () => {}, ist es ein Actionoder ein MethodInvoker? Der Compiler kann es nicht sagen.

Wenn ich einen Delegatentyp deklariere, der ein stringArgument nimmt und einen zurückgibt bool, wie würde der Compiler dann wissen, dass Sie wirklich einen Func<string, bool>anstelle meines Delegatentyps wollten ? Der Delegatentyp kann nicht abgeleitet werden.

Stephen Cleary
quelle
2

Die folgenden Punkte stammen aus dem MSDN in Bezug auf implizit typisierte lokale Variablen:

  1. var kann nur verwendet werden, wenn eine lokale Variable in derselben Anweisung deklariert und initialisiert wird. Die Variable kann nicht mit null, einer Methodengruppe oder einer anonymen Funktion initialisiert werden.
  2. Das Schlüsselwort var weist den Compiler an, den Typ der Variablen aus dem Ausdruck auf der rechten Seite der Initialisierungsanweisung abzuleiten.
  3. Es ist wichtig zu verstehen, dass das Schlüsselwort var nicht "variante" bedeutet und nicht anzeigt, dass die Variable lose typisiert oder spät gebunden ist. Dies bedeutet lediglich, dass der Compiler den am besten geeigneten Typ ermittelt und zuweist.

MSDN-Referenz: Implizit typisierte lokale Variablen

Berücksichtigen Sie Folgendes in Bezug auf anonyme Methoden:

  1. Mit anonymen Methoden können Sie die Parameterliste weglassen.

MSDN-Referenz: Anonyme Methoden

Ich würde vermuten, dass der Compiler nicht richtig ableiten kann, welcher Typ am besten zuzuweisen ist, da die anonyme Methode tatsächlich unterschiedliche Methodensignaturen hat.

Nybbler
quelle
1

Mein Beitrag beantwortet nicht die eigentliche Frage, aber die zugrunde liegende Frage:

"Wie vermeide ich es, einen flüchtigen Typ wie tippen zu müssen Func<string, string, int, CustomInputType, bool, ReturnType>?" [1]

Als fauler / hackiger Programmierer, der ich bin, habe ich mit der Verwendung experimentiert Func<dynamic, object>- die einen einzelnen Eingabeparameter verwendet und ein Objekt zurückgibt.

Für mehrere Argumente können Sie es folgendermaßen verwenden:

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

Tipp: Sie können verwenden, Action<dynamic>wenn Sie kein Objekt zurückgeben müssen.

Ja, ich weiß, dass es wahrscheinlich gegen Ihre Programmierprinzipien verstößt, aber das macht für mich und wahrscheinlich einige Python-Codierer Sinn.

Ich bin ziemlich unerfahren bei Delegierten ... wollte nur mitteilen, was ich gelernt habe.


[1] Dies setzt voraus, dass Sie keine Methode aufrufen, für die ein vordefinierter FuncParameter erforderlich ist. In diesem Fall müssen Sie diese flüchtige Zeichenfolge eingeben: /

Ambrose Leung
quelle
0

Wie ist das?

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