Pourquoi certaines expressions lambda C # se compilent-elles en méthodes statiques?

122

Comme vous pouvez le voir dans le code ci-dessous, j'ai déclaré un Action<>objet en tant que variable.

Quelqu'un pourrait-il me faire savoir pourquoi ce délégué de méthode d'action se comporte comme une méthode statique?

Pourquoi revient-il truedans le code suivant?

Code:

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

    Console.WriteLine(actionMethod.Method.IsStatic);

    Console.Read();
}

Production:

exemple de sortie d'échantillon

Nunu
la source

Réponses:

153

C'est probablement parce qu'il n'y a pas de fermetures, par exemple:

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

Cela produira falsepour withClosureet truepour withoutClosure.

Lorsque vous utilisez une expression lambda, le compilateur crée une petite classe pour contenir votre méthode, cela compilerait quelque chose comme ce qui suit (l'implémentation réelle varie très probablement légèrement):

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

Vous pouvez voir que les Action<string>instances résultantes pointent réellement vers des méthodes sur ces classes générées.

Lukazoïde
la source
4
+1. Peut confirmer - sans fermeture, ils sont de parfaits candidats pour les staticméthodes.
Simon Whitehead
3
J'allais juste suggérer que cette question avait besoin d'être élargie, je suis revenu et c'était là. Très instructif - super pour voir ce que fait le compilateur sous les couvertures.
Liath
4
@Liath Ildasmest vraiment utile pour comprendre ce qui se passe réellement, j'ai tendance à utiliser l' ILonglet LINQPadpour examiner de petits échantillons.
Lukazoid le
@Lukazoid Pouvez-vous nous dire comment avez-vous obtenu cette sortie de compilateur? ILDASM ne donnera pas une telle sortie .. Par n'importe quel outil OU logiciel?
nunu
8
@nunu Dans cet exemple, j'ai utilisé l' ILonglet LINQPadet déduit le C #. Certaines options pour obtenir l'équivalent C # réel de la sortie compilée consisteraient à utiliser ILSpyou Reflectorsur l'assembly compilé, vous devrez probablement désactiver certaines options qui tenteront d'afficher les lambdas et non les classes générées par le compilateur.
Lukazoid
20

La "méthode d'action" n'est statique qu'en tant qu'effet secondaire de l'implémentation. Il s'agit d'un cas de méthode anonyme sans variables capturées. Comme il n'y a pas de variables capturées, la méthode n'a pas d'exigences de durée de vie supplémentaires au-delà de celles des variables locales en général. S'il faisait référence à d'autres variables locales, sa durée de vie s'étend à la durée de vie de ces autres variables (voir sec. L.1.7, Variables locales , et sec. N.15.5.1, Variables externes capturées , dans la spécification C # 5.0).

Notez que la spécification C # ne parle que des méthodes anonymes converties en "arbres d'expression", et non en "classes anonymes". Bien que l'arborescence d'expression puisse être représentée comme des classes C # supplémentaires, par exemple, dans le compilateur Microsoft, cette implémentation n'est pas requise (comme reconnu par la sec. M.5.3 dans la spécification C # 5.0). Par conséquent, il n'est pas défini si la fonction anonyme est statique ou non. De plus, la section K.6 laisse beaucoup de place aux détails des arbres d'expression.

Peter O.
la source
2
+1 ce comportement ne devrait probablement pas être invoqué, pour les raisons indiquées; c'est vraiment un détail de mise en œuvre.
Lukazoid
18

Le comportement de mise en cache des délégués a été modifié dans Roslyn. Auparavant, comme indiqué, toute expression lambda qui ne capturait pas de variables était compilée dans une staticméthode sur le site d'appel. Roslyn a changé ce comportement. Désormais, tout lambda, qui capture ou non des variables, est transformé en classe d'affichage:

Compte tenu de cet exemple:

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

Sortie du compilateur natif:

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

Les changements de comportement de mise en cache des délégués dans Roslyn expliquent pourquoi cette modification a été apportée.

Yuval Itzchakov
la source
2
Merci, je me demandais pourquoi ma méthode Func <int> f = () => 5 n'était pas statique
vc 74
1

La méthode n'a pas de fermetures et fait également référence à une méthode statique elle-même (Console.WriteLine), donc je m'attendrais à ce qu'elle soit statique. La méthode déclarera un type anonyme englobant pour une fermeture, mais dans ce cas, ce n'est pas obligatoire.

Mel Padden
la source