web-dev-qa-db-fra.com

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

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 renvoie-t-il true dans 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();
}

Sortie:

example output of sample

121
nunu

Cela est probablement dû au fait 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 affichera false pour withClosure et true pour withoutClosure.

Lorsque vous utilisez une expression lambda, le compilateur crée une petite classe pour contenir votre méthode, cela se compilerait comme 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 les instances Action<string> Résultantes pointer vers des méthodes sur ces classes générées.

152
Lukazoid

La "méthode d'action" n'est statique que comme effet secondaire de l'implémentation. C'est le cas d'une méthode anonyme sans variable capturée. Puisqu'il n'y a aucune variable capturée, 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 a fait 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, Capturé variables externes, dans la spécification C # 5.0).

Notez que la spécification C # ne parle que des méthodes anonymes converties en "arborescences d'expression", pas 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 section 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 quant aux détails des arbres d'expression.

20
Peter O.

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 méthode static 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:

Étant donné 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);
        }
    }
}

Délégation des changements de comportement de mise en cache dans Roslyn explique pourquoi cette modification a été effectuée.

17
Yuval Itzchakov

La méthode n'a pas de fermeture 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, il n'est pas requis.

1
Mel Padden

À partir de C # 6, ce sera toujours par défaut aux méthodes d'instance maintenant, et ne sera jamais statique (donc actionMethod.Method.IsStatic sera toujours faux).

Voir ici: Pourquoi un lambda sans capture est-il passé d'une statique en C # 5 à une méthode d'instance en C # 6?

et ici: Différence dans l'évaluation de l'expression lambda statique du compilateur CSC et Roslyn?

1
James Wilkins