Как зафиксированное значение в анонимных методах реализовано в .NET - PullRequest
4 голосов
/ 13 апреля 2009

Меня интересует фактическая реализация .NET и решение, стоящее за ней.

Например, в Java все захваченные значения, используемые в анонимных классах, должны быть окончательными. Это требование, по-видимому, отброшено в .NET.

Кроме того, есть ли разница в реализации захваченных значений для типов значений в отличие от ссылочных типов?

Спасибо

1 Ответ

9 голосов
/ 13 апреля 2009

Самый простой способ узнать, как это реализовано, это попробовать. Напишите некоторый код, который использует захваченную переменную, скомпилируйте его, а затем посмотрите на него в Reflector . Обратите внимание, что записывается переменная , а не значение . Это одно из больших различий между Java и C # в этой области.

Основная идея заключается в том, что каждый уровень области действия, содержащий хотя бы одну захваченную переменную, приводит к созданию нового класса с полями для захваченных переменных. Если существует более одного уровня, то у внутренней области также есть поле для следующей области, и так далее. Подлинные локальные переменные в стеке становятся ссылками на экземпляры автоматически сгенерированных классов.

Вот пример:

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        List<Action> actions = new List<Action>();

        for (int i=0; i < 5; i++)
        {
            int copyOfI = i;

            for (int j=0; j < 5; j++)
            {
                int copyOfJ = j;

                actions.Add(delegate
                {
                    Console.WriteLine("{0} {1}", copyOfI, copyOfJ);
                });
            }
        }

        foreach (Action action in actions)
        {
            action();
        }        
    }
}

(Вы получите другие результаты, если вы не берете копию, конечно - экспериментируйте!) Это скомпилировано в код, подобный следующему:

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        List<Action> actions = new List<Action>();

        for (int i=0; i < 5; i++)
        {
            OuterScope outer = new OuterScope();
            outer.copyOfI = i;

            for (int j=0; j < 5; j++)
            {
                InnerScope inner = new InnerScope();
                inner.outer = outer;
                inner.copyOfJ = j;

                actions.Add(inner.Action);
            }
        }

        foreach (Action action in actions)
        {
            action();
        }        
    }

    class OuterScope
    {
        public int copyOfI;
    }

    class InnerScope
    {
        public int copyOfJ;
        public OuterScope outer;

        public void Action()
        {
            Console.WriteLine("{0} {1}", outer.copyOfI, copyOfJ);
        }
    }
}

Каждая ссылка на захваченную переменную в конечном итоге проходит через экземпляр сгенерированного класса, поэтому это не просто одноразовая копия. (Хорошо, в этом случае ничто другое в коде не использует захваченные переменные, но вы можете легко представить, что это может.) Обратите внимание, что для любой итерации внешнего цикла все пять новых экземпляров совместно используют один экземпляр OuterScope. Возможно, вы захотите попробовать поиграть с дополнительным кодом в делегате, чтобы увидеть, как это повлияет на ситуацию - если делегат изменится copyofI, это изменение будет видно в следующем делегате; изменения в copyOfJ не будут видны, потому что следующий делегат будет использовать отдельный экземпляр InnerScope.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...