Подробное объяснение переменной захвата в замыканиях

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

Я ищу четкое объяснение:

  1. как локальные переменные на самом деле плен.
  2. разница (если есть) между типами значений захвата и ссылочными типами.
  3. и есть ли бокс происходящее в отношении типов значений.

Я бы предпочел ответ с точки зрения значений и указателей (ближе к сердцу того, что происходит внутри), хотя я приму четкий ответ, включающий значения и ссылки.

1 ответов


  1. сложно. Придет на него через минуту.
  2. нет никакой разницы - в обоих случаях, это сама переменная, которая захватила.
  3. нет, бокс не происходит.

вероятно, проще всего продемонстрировать, как работает захват на примере...

вот некоторый код, использующий лямбда-выражение, которое захватывает одну переменную:

using System;

class Test
{
    static void Main()
    {
        Action action = CreateShowAndIncrementAction();
        action();
        action();
    }

    static Action CreateShowAndIncrementAction()
    {
        Random rng = new Random();
        int counter = rng.Next(10);
        Console.WriteLine("Initial value for counter: {0}", counter);
        return () =>
        {
            Console.WriteLine(counter);
            counter++;
        };
    }
}

теперь вот что компилятор делает для вас-за исключением того, что он будет используйте "невыразимые" имена, которые на самом деле не могли возникнуть в C#.

using System;

class Test
{
    static void Main()
    {
        Action action = CreateShowAndIncrementAction();
        action();
        action();
    }

    static Action CreateShowAndIncrementAction()
    {
        ActionHelper helper = new ActionHelper();        
        Random rng = new Random();
        helper.counter = rng.Next(10);
        Console.WriteLine("Initial value for counter: {0}", helper.counter);

        // Converts method group to a delegate, whose target will be a
        // reference to the instance of ActionHelper
        return helper.DoAction;
    }

    class ActionHelper
    {
        // Just for simplicity, make it public. I don't know if the
        // C# compiler really does.
        public int counter;

        public void DoAction()
        {
            Console.WriteLine(counter);
            counter++;
        }
    }
}

если вы захватываете переменные, объявленные в цикле, вы получите новый экземпляр ActionHelper для каждой итерации цикла-таким образом, вы эффективно захватываете разные "экземпляры" переменных.

это становится более сложным, когда вы захватываете переменные из разных областей... дайте мне знать, если вы действительно хотите такого уровня детализации, или вы можете просто написать код, декомпилировать его в Reflector и следуйте ему до конца:)

обратите внимание, как:

  • нет никакого бокса
  • нет никаких указателей или любого другого небезопасного кода

EDIT: вот пример двух делегатов, совместно использующих переменную. Один делегат показывает текущее значение counter, другой увеличивает его:

using System;

class Program
{
    static void Main(string[] args)
    {
        var tuple = CreateShowAndIncrementActions();
        var show = tuple.Item1;
        var increment = tuple.Item2;

        show(); // Prints 0
        show(); // Still prints 0
        increment();
        show(); // Now prints 1
    }

    static Tuple<Action, Action> CreateShowAndIncrementActions()
    {
        int counter = 0;
        Action show = () => { Console.WriteLine(counter); };
        Action increment = () => { counter++; };
        return Tuple.Create(show, increment);
    }
}

... и расширение:

using System;

class Program
{
    static void Main(string[] args)
    {
        var tuple = CreateShowAndIncrementActions();
        var show = tuple.Item1;
        var increment = tuple.Item2;

        show(); // Prints 0
        show(); // Still prints 0
        increment();
        show(); // Now prints 1
    }

    static Tuple<Action, Action> CreateShowAndIncrementActions()
    {
        ActionHelper helper = new ActionHelper();
        helper.counter = 0;
        Action show = helper.Show;
        Action increment = helper.Increment;
        return Tuple.Create(show, increment);
    }

    class ActionHelper
    {
        public int counter;

        public void Show()
        {
            Console.WriteLine(counter);
        }

        public void Increment()
        {
            counter++;
        }
    }
}