Проблема с делегатами в C # - PullRequest
7 голосов
/ 02 ноября 2009

В следующей программе DummyMethod всегда печатает 5. Но если вместо этого мы используем закомментированный код, мы получаем разные значения (то есть 1, 2, 3, 4). Кто-нибудь может объяснить, почему это происходит?

        delegate int Methodx(object obj);

        static int DummyMethod(int i)
        {
            Console.WriteLine("In DummyMethod method i = " + i);
            return i + 10;
        }

        static void Main(string[] args)
        {    
            List<Methodx> methods = new List<Methodx>();

            for (int i = 0; i < 5; ++i)
            {
                methods.Add(delegate(object obj) { return DummyMethod(i); });
            }

            //methods.Add(delegate(object obj) { return DummyMethod(1); });
            //methods.Add(delegate(object obj) { return DummyMethod(2); });
            //methods.Add(delegate(object obj) { return DummyMethod(3); });
            //methods.Add(delegate(object obj) { return DummyMethod(4); });

            foreach (var method in methods)
            {
                int c = method(null);
                Console.WriteLine("In main method c = " + c);
            }
        }

Также, если используется следующий код, я получаю желаемый результат.

        for (int i = 0; i < 5; ++i)
        {
            int j = i;
            methods.Add(delegate(object obj) { return DummyMethod(j); });
        } 

Ответы [ 4 ]

17 голосов
/ 02 ноября 2009

Проблема в том, что вы захватываете одну и ту же переменную i в каждом делегате, который к концу цикла просто имеет значение 5.

Вместо этого вы хотите, чтобы каждый делегат захватывал разные переменные, что означает объявление новой переменной в цикле:

for (int i = 0; i < 5; ++i)
{
    int localCopy = i;
    methods.Add(delegate(object obj) { return DummyMethod(localCopy); });
}

Это довольно распространенная "ошибка" - вы можете прочитать немного больше о захваченных переменных и замыканиях в моей статье замыканий .

4 голосов
/ 02 ноября 2009

Если вы посмотрите на сгенерированный код (используя Reflector), вы увидите разницу:

private static void Method2()
{
    List<Methodx> list = new List<Methodx>();
    Methodx item = null;
    <>c__DisplayClassa classa = new <>c__DisplayClassa();
    classa.i = 0;
    while (classa.i < 5)
    {
        if (item == null)
        {
            item = new Methodx(classa.<Method2>b__8);
        }
        list.Add(item);
        classa.i++;
    }
    foreach (Methodx methodx2 in list)
    {
        Console.WriteLine("In main method c = " + methodx2(null));
    }
}

Когда вы используете исходный код, он создает временный класс в фоновом режиме, этот класс содержит ссылку на переменную «i», поэтому, согласно ответу Джона, вы видите только окончательное значение этого.

private sealed class <>c__DisplayClassa
{
    // Fields
    public int i;

    // Methods
    public <>c__DisplayClassa();
    public int <Method2>b__8(object obj);
}

Я действительно рекомендую просмотреть код в Reflector , чтобы увидеть, что происходит, как я понял смысл захваченных переменных. Убедитесь, что для параметра «Оптимизация кода» установлено значение «.NET 1.0» в меню «Параметры», в противном случае все скрытые действия будут скрыты.

4 голосов
/ 02 ноября 2009

Эта статья, вероятно, поможет вам понять, что происходит (то есть, что такое замыкание ): http://blogs.msdn.com/oldnewthing/archive/2006/08/02/686456.aspx

2 голосов
/ 02 ноября 2009

Я думаю, это потому, что переменная i помещается в кучу (это захваченная переменная )

Взгляните на этот ответ .

...