Perché alcune espressioni lambda C # vengono compilate in metodi statici?


122

Come puoi vedere nel codice sottostante, ho dichiarato un Action<>oggetto come variabile.

Qualcuno potrebbe farmi sapere perché questo delegato del metodo di azione si comporta come un metodo statico?

Perché ritorna truenel codice seguente?

Codice:

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

    Console.WriteLine(actionMethod.Method.IsStatic);

    Console.Read();
}

Produzione:

output di esempio del campione

Risposte:


153

Questo è molto probabile perché non ci sono chiusure, ad esempio:

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

Questo produrrà falseper withClosuree trueper withoutClosure.

Quando usi un'espressione lambda, il compilatore crea una piccola classe per contenere il tuo metodo, questo si compila in qualcosa di simile al seguente (l'implementazione effettiva molto probabilmente varia leggermente):

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

È possibile vedere che le Action<string>istanze risultanti puntano effettivamente a metodi su queste classi generate.


4
+1. Può confermare: senza una chiusura sono candidati perfetti per i staticmetodi.
Simon Whitehead

3
Volevo solo suggerire che questa domanda necessitava di qualche espansione, sono tornato ed eccola lì. Molto istruttivo - fantastico vedere cosa sta facendo il compilatore sotto le coperte.
Liath

4
@Liath Ildasmè davvero utile per capire cosa sta effettivamente succedendo, tendo ad usare la ILscheda di LINQPadper esaminare piccoli campioni.
Lukazoid

@Lukazoid Puoi dirci come hai ottenuto l'output del compilatore? ILDASM non darà tale output .. Con qualsiasi strumento O software?
nunu

8
@nunu In questo esempio, ho usato la ILscheda LINQPade ho dedotto il C #. Alcune opzioni per ottenere l'equivalente C # effettivo dell'output compilato sarebbero da usare ILSpyo Reflectorsull'assembly compilato, molto probabilmente sarà necessario disabilitare alcune opzioni che tenteranno di visualizzare i lambda e non le classi generate dal compilatore.
Lukazoid

20

Il "metodo di azione" è statico solo come effetto collaterale dell'implementazione. Questo è un caso di un metodo anonimo senza variabili acquisite. Poiché non sono presenti variabili acquisite, il metodo non ha requisiti di durata aggiuntivi oltre a quelli per le variabili locali in generale. Se fa riferimento ad altre variabili locali, la sua durata si estende alla durata di quelle altre variabili (vedere la sezione L.1.7, Variabili locali e la sezione N.15.5.1, Variabili esterne acquisite , nella specifica C # 5.0).

Si noti che la specifica C # parla solo di metodi anonimi convertiti in "alberi delle espressioni", non "classi anonime". Sebbene l'albero delle espressioni possa essere rappresentato come classi C # aggiuntive, ad esempio, nel compilatore Microsoft, questa implementazione non è richiesta (come riconosciuto dalla sezione M.5.3 nella specifica C # 5.0). Pertanto, non è definito se la funzione anonima è statica o meno. Inoltre, la sezione K.6 lascia molto aperta sui dettagli degli alberi di espressione.


2
+1 molto probabilmente non si dovrebbe fare affidamento su questo comportamento, per i motivi indicati; è molto un dettaglio di implementazione.
Lukazoid

18

Il comportamento di memorizzazione nella cache dei delegati è stato modificato in Roslyn. In precedenza, come affermato, qualsiasi espressione lambda che non catturava variabili veniva compilata in un staticmetodo nel sito della chiamata. Roslyn ha cambiato questo comportamento. Ora, qualsiasi lambda, che cattura o meno variabili, viene trasformato in una classe di visualizzazione:

Dato questo esempio:

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

Output del compilatore nativo:

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

Delegare le modifiche al comportamento della memorizzazione nella cache in Roslyn spiega perché è stata apportata questa modifica.


2
Grazie, mi chiedevo perché il mio metodo Func <int> f = () => 5 non fosse statico
vc 74


1

Il metodo non ha chiusure e fa riferimento anche a un metodo statico stesso (Console.WriteLine), quindi mi aspetto che sia statico. Il metodo dichiarerà un tipo anonimo di chiusura per una chiusura, ma in questo caso non è richiesto.

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.