Что такое «замыкания» в .NET? - PullRequest
185 голосов
/ 09 января 2009

Что такое замыкание ? У нас они есть в .NET?


Если они существуют в .NET, не могли бы вы предоставить фрагмент кода (желательно на C #), объясняющий это?


РЕДАКТИРОВАТЬ: я просмотрел статью Джона Скита , чтобы понять, что такое замыкания и как их использовать в .NET.

Ответы [ 12 ]

237 голосов
/ 09 января 2009

У меня есть статья на эту тему . (Там много примеров.)

По сути, замыкание - это блок кода, который может быть выполнен позднее, но который поддерживает среду, в которой он был впервые создан, т.е. он все еще может использовать локальные переменные и т. Д. Метода, который его создал даже после того, как этот метод завершил выполнение.

Общая функция замыканий реализована в C # анонимными методами и лямбда-выражениями.

Вот пример использования анонимного метода:

using System;

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

    static Action CreateAction()
    {
        int counter = 0;
        return delegate
        {
            // Yes, it could be done in one statement; 
            // but it is clearer like this.
            counter++;
            Console.WriteLine("counter={0}", counter);
        };
    }
}

Выход:

counter=1
counter=2

Здесь мы видим, что действие, возвращаемое CreateAction, все еще имеет доступ к переменной counter и может действительно увеличивать его, даже если сам CreateAction завершил работу.

20 голосов
/ 22 августа 2011

Если вам интересно посмотреть, как C # реализует Closure, прочитайте «Я знаю ответ (его 42) блог»

Компилятор генерирует класс в фоновом режиме для инкапсуляции аномального метода и переменной j

[CompilerGenerated]
private sealed class <>c__DisplayClass2
{
    public <>c__DisplayClass2();
    public void <fillFunc>b__0()
    {
       Console.Write("{0} ", this.j);
    }
    public int j;
}

для функции:

static void fillFunc(int count) {
    for (int i = 0; i < count; i++)
    {
        int j = i;
        funcArr[i] = delegate()
                     {
                         Console.Write("{0} ", j);
                     };
    } 
}

Превращение в:

private static void fillFunc(int count)
{
    for (int i = 0; i < count; i++)
    {
        Program.<>c__DisplayClass1 class1 = new Program.<>c__DisplayClass1();
        class1.j = i;
        Program.funcArr[i] = new Func(class1.<fillFunc>b__0);
    }
}
9 голосов
/ 09 января 2009

Замыкания - это функциональные значения, которые содержат значения переменных из их первоначальной области видимости. C # может использовать их в виде анонимных делегатов.

Для очень простого примера, возьмите этот код C #:

    delegate int testDel();

    static void Main(string[] args)
    {
        int foo = 4;
        testDel myClosure = delegate()
        {
            return foo;
        };
        int bar = myClosure();

    }

В конце этого бара будет установлено значение 4, и делегат myClosure может быть передан для использования в другом месте в программе.

Замыкания могут использоваться для многих полезных вещей, таких как отложенное выполнение или для упрощения интерфейсов - LINQ в основном построен с использованием замыканий. Наиболее непосредственный способ, которым это пригодится большинству разработчиков, - это добавление обработчиков событий в динамически создаваемые элементы управления - вы можете использовать замыкания для добавления поведения при создании экземпляра элемента управления вместо хранения данных в другом месте.

7 голосов
/ 09 января 2009
Func<int, int> GetMultiplier(int a)
{
     return delegate(int b) { return a * b; } ;
}
//...
var fn2 = GetMultiplier(2);
var fn3 = GetMultiplier(3);
Console.WriteLine(fn2(2));  //outputs 4
Console.WriteLine(fn2(3));  //outputs 6
Console.WriteLine(fn3(2));  //outputs 6
Console.WriteLine(fn3(3));  //outputs 9

Замыкание - это анонимная функция, переданная за пределы функции, в которой оно создано. Он поддерживает любые переменные из функции, в которой он создан, и использует его.

4 голосов
/ 09 января 2009

Вот надуманный пример для C #, который я создал из аналогичного кода в JavaScript:

public delegate T Iterator<T>() where T : class;

public Iterator<T> CreateIterator<T>(IList<T> x) where T : class
{
        var i = 0; 
        return delegate { return (i < x.Count) ? x[i++] : null; };
}

Итак, вот код, который показывает, как использовать приведенный выше код ...

var iterator = CreateIterator(new string[3] { "Foo", "Bar", "Baz"});

// So, although CreateIterator() has been called and returned, the variable 
// "i" within CreateIterator() will live on because of a closure created 
// within that method, so that every time the anonymous delegate returned 
// from it is called (by calling iterator()) it's value will increment.

string currentString;    
currentString = iterator(); // currentString is now "Foo"
currentString = iterator(); // currentString is now "Bar"
currentString = iterator(); // currentString is now "Baz"
currentString = iterator(); // currentString is now null

Надеюсь, это несколько полезно.

2 голосов
/ 16 августа 2016

Закрытие - это когда функция определяется внутри другой функции (или метода) и использует переменные из родительского метода . Такое использование переменных, которые находятся в методе и заключены в определенную в нем функцию, называется закрытием.

У Марка Симанна есть несколько интересных примеров замыканий в его посте в блоге , где он проводит параллель между ООП и функциональным программированием.

И чтобы сделать это более подробно

var workingDirectory = new DirectoryInfo(Environment.CurrentDirectory);//when this variable
Func<int, string> read = id =>
    {
        var path = Path.Combine(workingDirectory.FullName, id + ".txt");//is used inside this function
        return File.ReadAllText(path);
    };//the entire process is called a closure.
2 голосов
/ 09 января 2009

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

Вот простой пример:
Метод List.Find может принимать и выполнять фрагмент кода (замыкание), чтобы найти элемент списка.

// Passing a block of code as a function argument
List<int> ints = new List<int> {1, 2, 3};
ints.Find(delegate(int value) { return value == 1; });

Используя синтаксис C # 3.0, мы можем записать это как:

ints.Find(value => value == 1);
1 голос
/ 09 января 2009

Замыкания - это фрагменты кода, которые ссылаются на переменную вне себя (из-под них в стеке), которые могут быть вызваны или выполнены позже (например, когда определено событие или делегат, и могут вызываться в неопределенном будущем). момент времени) ... Поскольку внешняя переменная, на которую ссылается кусок кода, может выйти из области видимости (и в противном случае была бы потеряна), тот факт, что на нее ссылается кусок кода (называемый замыканием), сообщает среде выполнения «держать» эту переменную в области видимости, пока она больше не понадобится фрагменту кода закрытия ...

0 голосов
/ 13 июня 2018

Если вы напишите встроенный анонимный метод (C # 2) или (предпочтительно) лямбда-выражение (C # 3 +), фактический метод все еще создается. Если этот код использует локальную переменную внешней области видимости - вам все равно нужно как-то передать эту переменную в метод.

например. возьмем это предложение Linq Where (это простой метод расширения, который передает лямбда-выражение):

var i = 0;
var items = new List<string>
{
    "Hello","World"
};   
var filtered = items.Where(x =>
// this is a predicate, i.e. a Func<T, bool> written as a lambda expression
// which is still a method actually being created for you in compile time 
{
    i++;
    return true;
});

если вы хотите использовать i в этом лямбда-выражении, вы должны передать его этому созданному методу.

Итак, первый вопрос, который возникает: должен быть передан по значению или по ссылке?

Передача по ссылке (я думаю) более предпочтительна, поскольку вы получаете доступ на чтение / запись к этой переменной (и это то, что делает C #; я думаю, что команда в Microsoft взвесила все за и против и пошла с побочной ссылкой; на статья Джона Скита , Java пошла с побочной стоимостью).

Но тогда возникает другой вопрос: Где выделить это i?

Должно ли оно фактически / естественно размещаться в стеке? Ну, если вы разместите его в стеке и передадите его по ссылке, могут быть ситуации, когда он переживает свой собственный кадр стека. Возьмите этот пример:

static void Main(string[] args)
{
    Outlive();
    var list = whereItems.ToList();
    Console.ReadLine();
}

static IEnumerable<string> whereItems;

static void Outlive()
{
    var i = 0;
    var items = new List<string>
    {
        "Hello","World"
    };            
    whereItems = items.Where(x =>
    {
        i++;
        Console.WriteLine(i);
        return true;
    });            
}

Лямбда-выражение (в предложении Where) снова создает метод, который ссылается на i. Если i размещен в стеке Outlive, то к тому времени, когда вы перечислите whereItems, i, использованный в сгенерированном методе, будет указывать на i из Outlive, то есть на место в стеке, которое больше не доступно.

Хорошо, тогда нам нужно это в куче.

Итак, что делает компилятор C # для поддержки этого встроенного анонимного / лямбда-выражения, использует то, что называется " Closures ": он создает класс в куче с именем ( довольно плохо ) DisplayClass, который имеет поле, содержащее i, и функцию, которая фактически использует его.

Что-то, что было бы эквивалентно этому (вы можете увидеть IL, сгенерированный с использованием ILSpy или ILDASM):

class <>c_DisplayClass1
{
    public int i;

    public bool <GetFunc>b__0()
    {
        this.i++;
        Console.WriteLine(i);
        return true;
    }
}

Он создает экземпляр этого класса в локальной области видимости и заменяет любой код, относящийся к i или лямбда-выражению, этим экземпляром замыкания. Итак, всякий раз, когда вы используете i в своем коде «локальной области видимости», где я был определен, вы фактически используете это поле экземпляра DisplayClass.

Так что, если бы я изменил "local" i в методе main, он фактически изменит _DisplayClass.i;

т.е.

var i = 0;
var items = new List<string>
{
    "Hello","World"
};  
var filtered = items.Where(x =>
{
    i++;
    return true;
});
filtered.ToList(); // will enumerate filtered, i = 2
i = 10;            // i will be overwriten with 10
filtered.ToList(); // will enumerate filtered again, i = 12
Console.WriteLine(i); // should print out 12

он напечатает 12, так как «i = 10» переходит в это поле dispalyclass и изменяет его непосредственно перед вторым перечислением.

Хороший источник по теме - это Модуль Барт де Смет Pluralsight (требует регистрации) (также игнорируйте его ошибочное использование термина «Подъем» - что (я думаю) он имеет в виду, что местный переменная (т. е. i) изменяется для ссылки на новое поле DisplayClass).


В других новостях, кажется, есть некоторое заблуждение, что "Замыкания" связаны с циклами - как я понимаю "Замыкания" - это НЕ понятие, связанное с циклами , а скорее с анонимными методами / лямбда-выражениями использование локальных переменных области действия - хотя некоторые вопросы с подвохом используют циклы для демонстрации этого.

0 голосов
/ 23 декабря 2017

Просто неожиданно, простой и более понятный ответ из слов C # 7.0.

Предварительное требование, которое вы должны знать : лямбда-выражение может ссылаться на локальные переменные и параметры метода в котором оно определено (внешние переменные).

    static void Main()
    {
    int factor = 2;
   //Here factor is the variable that takes part in lambda expression.
    Func<int, int> multiplier = n => n * factor;
    Console.WriteLine (multiplier (3)); // 6
    }

Действительная часть : Внешние переменные, на которые ссылается лямбда-выражение, называются захваченными переменными. Лямбда-выражение, которое захватывает переменные, называется замыканием.

Последняя отметка, которую следует отметить : Захваченные переменные оцениваются при фактическом вызове делегата, а не при записи переменных:

int factor = 2;
Func<int, int> multiplier = n => n * factor;
factor = 10;
Console.WriteLine (multiplier (3)); // 30
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...