Warum werden einige C # -Lambda-Ausdrücke mit statischen Methoden kompiliert?

122

Wie Sie im folgenden Code sehen können, habe ich ein Action<>Objekt als Variable deklariert .

Würde mich bitte jemand wissen lassen, warum sich dieser Delegat für Aktionsmethoden wie eine statische Methode verhält?

Warum wird trueder folgende Code zurückgegeben?

Code:

public static void Main(string[] args)
{
    Action<string> actionMethod = s => { Console.WriteLine("My Name is " + s); };

    Console.WriteLine(actionMethod.Method.IsStatic);

    Console.Read();
}

Ausgabe:

Beispielausgabe der Probe

Nunu
quelle

Antworten:

153

Dies liegt höchstwahrscheinlich daran, dass es keine Schließungen gibt, zum Beispiel:

int age = 25;
Action<string> withClosure = s => Console.WriteLine("My name is {0} and I am {1} years old", s, age);
Action<string> withoutClosure = s => Console.WriteLine("My name is {0}", s);
Console.WriteLine(withClosure.Method.IsStatic);
Console.WriteLine(withoutClosure.Method.IsStatic);

Dies wird falsefür withClosureund truefür ausgegeben withoutClosure.

Wenn Sie einen Lambda-Ausdruck verwenden, erstellt der Compiler eine kleine Klasse, die Ihre Methode enthält. Dies würde sich wie folgt kompilieren lassen (die tatsächliche Implementierung variiert höchstwahrscheinlich geringfügig):

private class <Main>b__0
{
    public int age;
    public void withClosure(string s)
    {
        Console.WriteLine("My name is {0} and I am {1} years old", s, age)
    }
}

private static class <Main>b__1
{
    public static void withoutClosure(string s)
    {
        Console.WriteLine("My name is {0}", s)
    }
}

public static void Main()
{
    var b__0 = new <Main>b__0();
    b__0.age = 25;
    Action<string> withClosure = b__0.withClosure;
    Action<string> withoutClosure = <Main>b__1.withoutClosure;
    Console.WriteLine(withClosure.Method.IsStatic);
    Console.WriteLine(withoutClosure.Method.IsStatic);
}

Sie können sehen, dass die resultierenden Action<string>Instanzen tatsächlich auf Methoden für diese generierten Klassen verweisen.

Lukazoid
quelle
4
+1. Kann bestätigen - ohne Abschluss sind sie perfekte Kandidaten für staticMethoden.
Simon Whitehead
3
Ich wollte nur vorschlagen, dass diese Frage etwas erweitert werden muss, ich kehrte zurück und da war es. Sehr informativ - großartig zu sehen, was der Compiler unter der Decke tut.
Liath
4
@Liath Ildasmist wirklich nützlich, um zu verstehen, was tatsächlich vor sich geht. Ich benutze normalerweise die ILRegisterkarte von LINQPad, um kleine Proben zu untersuchen.
Lukazoid
@Lukazoid Würden Sie uns bitte sagen, wie Sie diese Compiler-Ausgabe erhalten haben? ILDASM gibt keine solche Ausgabe. Mit irgendeinem Tool oder Software?
Nunu
8
@nunu In diesem Beispiel habe ich die ILRegisterkarte LINQPadC # verwendet und daraus abgeleitet. Einige Optionen, um das tatsächliche C # -Äquivalent der kompilierten Ausgabe zu erhalten, sind die Verwendung ILSpyoder Reflectordie kompilierte Assembly. Sie müssten höchstwahrscheinlich einige Optionen deaktivieren, mit denen versucht wird, die Lambdas und nicht die vom Compiler generierten Klassen anzuzeigen.
Lukazoid
20

Die "Aktionsmethode" ist nur als Nebeneffekt der Implementierung statisch. Dies ist ein Fall einer anonymen Methode ohne erfasste Variablen. Da keine erfassten Variablen vorhanden sind, stellt die Methode keine zusätzlichen Anforderungen an die Lebensdauer, die über die für lokale Variablen im Allgemeinen hinausgehen. Wenn auf andere lokale Variablen verwiesen wurde, erstreckt sich seine Lebensdauer auf die Lebensdauer dieser anderen Variablen (siehe Abschnitt L.1.7, Lokale Variablen , und Abschnitt N.15.5.1, Erfasste äußere Variablen , in der C # 5.0-Spezifikation).

Beachten Sie, dass die C # -Spezifikation nur von anonymen Methoden spricht, die in "Ausdrucksbäume" konvertiert werden, nicht von "anonymen Klassen". Während der Ausdrucksbaum beispielsweise im Microsoft-Compiler als zusätzliche C # -Klassen dargestellt werden könnte, ist diese Implementierung nicht erforderlich (wie in Abschnitt M.5.3 in der C # 5.0-Spezifikation bestätigt). Daher ist nicht definiert, ob die anonyme Funktion statisch ist oder nicht. Darüber hinaus lässt Abschnitt K.6 die Details der Expressionsbäume offen.

Peter O.
quelle
2
+1 Auf dieses Verhalten sollte aus den genannten Gründen höchstwahrscheinlich nicht vertraut werden. Es ist sehr viel ein Implementierungsdetail.
Lukazoid
18

Das Caching-Verhalten von Delegierten wurde in Roslyn geändert. Wie bereits erwähnt, wurde zuvor jeder Lambda-Ausdruck, der keine Variablen erfasst hat static, an der Aufrufstelle zu einer Methode kompiliert . Roslyn hat dieses Verhalten geändert. Jetzt wird jedes Lambda, das Variablen erfasst oder nicht, in eine Anzeigeklasse umgewandelt:

Angesichts dieses Beispiels:

public class C
{
    public void M()
    {
        var x = 5;
        Action<int> action = y => Console.WriteLine(y);
    }
}

Native Compiler-Ausgabe:

public class C
{
    [CompilerGenerated]
    private static Action<int> CS$<>9__CachedAnonymousMethodDelegate1;
    public void M()
    {
        if (C.CS$<>9__CachedAnonymousMethodDelegate1 == null)
        {
            C.CS$<>9__CachedAnonymousMethodDelegate1 = new Action<int>(C.<M>b__0);
        }
        Action<int> arg_1D_0 = C.CS$<>9__CachedAnonymousMethodDelegate1;
    }
    [CompilerGenerated]
    private static void <M>b__0(int y)
    {
        Console.WriteLine(y);
    }
}

Roslyn:

public class C
{
    [CompilerGenerated]
    private sealed class <>c__DisplayClass0
    {
        public static readonly C.<>c__DisplayClass0 CS$<>9__inst;
        public static Action<int> CS$<>9__CachedAnonymousMethodDelegate2;
        static <>c__DisplayClass0()
        {
            // Note: this type is marked as 'beforefieldinit'.
            C.<>c__DisplayClass0.CS$<>9__inst = new C.<>c__DisplayClass0();
        }
        internal void <M>b__1(int y)
        {
            Console.WriteLine(y);
        }
    }
    public void M()
    {
        Action<int> arg_22_0;
        if (arg_22_0 = C.
                       <>c__DisplayClass0.CS$<>9__CachedAnonymousMethodDelegate2 == null)
        {
            C.<>c__DisplayClass0.CS$<>9__CachedAnonymousMethodDelegate2 =
          new Action<int>(C.<>c__DisplayClass0.CS$<>9__inst.<M>b__1);
        }
    }
}

Änderungen des Caching-Verhaltens von Delegierten in Roslyn sprechen darüber, warum diese Änderung vorgenommen wurde.

Yuval Itzchakov
quelle
2
Danke, ich habe mich gefragt, warum die Methode meiner Func <int> f = () => 5 nicht statisch war
vc 74
1

Die Methode hat keine Abschlüsse und verweist auch auf eine statische Methode selbst (Console.WriteLine), daher würde ich erwarten, dass sie statisch ist. Die Methode deklariert einen einschließenden anonymen Typ für einen Abschluss, ist jedoch in diesem Fall nicht erforderlich.

Mel Padden
quelle