44 votes

Comment dire à une fonction lambda de capturer une copie plutôt qu'une référence en C#?

J'ai appris C#, et j'essaie de comprendre les lambdas. Dans cet exemple ci-dessous, cela affiche 10 dix fois.

class Program
{
    delegate void Action();
    static void Main(string[] args)
    {
        List actions = new List();

        for (int i = 0; i < 10; ++i )
            actions.Add(()=>Console.WriteLine(i));

        foreach (Action a in actions)
            a();
    }
}

Évidemment, la classe générée derrière la lambda stocke une référence ou pointeur à la variable int i, et assigne une nouvelle valeur à la même référence à chaque itération de la boucle. Y a-t-il un moyen de forcer la lambda à capturer une copie plutôt, comme la syntaxe C++0x

[&](){ ... } // Capture par référence

vs.

[=](){ ... } // Capture les copies

36voto

Eclipse Points 27662

La seule solution que j'ai pu trouver est de d'abord faire une copie locale :

for (int i = 0; i < 10; ++i)
{
    int copy = i;
    actions.Add(() => Console.WriteLine(copy));
}

Mais j'ai du mal à comprendre pourquoi mettre une copie à l'intérieur de la boucle for est différent d'avoir le lambda capturer i.

34voto

Tinister Points 3649

Ce que le compilateur fait, c'est qu'il extrait votre lambda et toutes les variables capturées par le lambda dans une classe imbriquée générée par le compilateur.

Après la compilation, votre exemple ressemble beaucoup à ceci :

class Programme
{
        délégué void Action();
        static void Main(string[] args)
        {
                List actions = new List();

                DisplayClass1 displayClass1 = new DisplayClass1();
                for (displayClass1.i = 0; displayClass1.i < 10; ++displayClass1.i )
                        actions.Add(new Action(displayClass1.Lambda));

                foreach (Action a in actions)
                        a();
        }

        class DisplayClass1
        {
                int i;
                void Lambda()
                {
                        Console.WriteLine(i);
                }
        }
}

En faisant une copie dans la boucle for, le compilateur génère de nouveaux objets à chaque itération, comme suit :

pour (int i = 0; i < 10; ++i)
{
    DisplayClass1 displayClass1 = new DisplayClass1();
    displayClass1.i = i;
    actions.Add(new Action(displayClass1.Lambda));
}

11voto

JaredPar Points 333733

La seule solution est de faire une copie locale et de la référencer dans la lambda. Toutes les variables en C# (et VB.Net) lorsqu'elles sont accédées dans une fermeture auront des sémantiques de référence par rapport aux sémantiques de copie/valeur. Il n'y a pas moyen de changer ce comportement dans l'une ou l'autre langue.

Remarque : En réalité, cela ne compile pas en tant que référence. Le compilateur élève la variable dans une classe de fermeture et redirige les accès de "i" vers un champ "i" à l'intérieur de la classe de fermeture donnée. Il est souvent plus facile de penser à cela comme à des sémantiques de référence.

5voto

Matt Brunell Points 7120

N'oubliez pas que les expressions lambda ne sont en réalité que du sucre syntaxique pour des méthodes anonymes.

Cela étant dit, ce que vous recherchez vraiment est comment les méthodes anonymes utilisent les variables locales dans une portée parente.

Voici un lien décrivant cela. http://www.codeproject.com/KB/cs/InsideAnonymousMethods.aspx#4

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X