Прежде всего, я думаю, что существует некоторая путаница в отношении того, что следует называть замыканием и тем, что следует называть лямбда-функцией . Я считаю, что правильным подходом является вызов синтаксического элемента в языке (например, (a, b) => a + b
в C #) лямбда-функции. Созданное значение: значение функции или делегат в C #.
Как реализовано в .NET (F # и C #), делегат на самом деле является ссылкой на некоторый метод в некотором классе. Причина в том, что для делегата , созданного с использованием синтаксиса лямбда-функции *1017*, может потребоваться некоторое состояние:
Func<int, int> CreateAdder(int num) {
return arg => arg + num;
}
Возвращенный делегат ссылается на некоторый (неназванный) объект, который хранит значение num
, а также тело лямбда-функции . Итак, что такое замыкание ? Закрытие - это объект, который сохраняет состояние, необходимое для запуска значения функции (или delgate ). В данном случае это безымянный объект, на который ссылается делегат и который сохраняет значение num
.
Вы также упомянули свободные и связанные переменные. Если вы посмотрите на лямбда-функцию в приведенном выше примере, она работает с двумя переменными. Переменная arg
объявлена как часть лямбда-функции . Это будет называться связанной переменной (в лямбда-функции ), потому что она объявлена как часть лямбда-функции. Переменная num
будет называться свободной переменной в лямбда-функции , поскольку она используется (но не объявляется!) В области действия лямбда-функции . Это происходит из внешней области видимости (в данном случае, из объявления метода).
Закрытие должно захватывать все свободные переменные в лямбда-функции . Это означает, что все переменные, которые используются внутри тела, но объявлены где-то еще, перехвачены. Однако компилятор C # не просто копирует текущее значение. Он превращает его в изменяемое поле, так что к нему можно получить доступ (и также мутировать) из всех функций, которые могут получить к нему доступ (и это также место, где оно становится сложным). Это было бы темой для длинного поста в блоге, но вот краткий пример, который вы можете использовать, чтобы поэкспериментировать с этим (используя Tuple
из .NET 4.0, если вы используете VS 2008, вы можете получить реализацию C # здесь, в Chapter03 / FunctionalCSharp ):
Tuple<Func<int>, Action<int>> CreateReaderAndWriter(int initial) {
int state = initial;
return Tuple.Create( (() => state),
(newState => { state = newState; }) );
}
Когда вы вызовете этот метод, вы получите две функции. Первый позволяет вам прочитать текущее состояние, а второй позволяет вам изменить его. Обратите внимание, что состояние является общим (потому что это та же изменяемая переменная)!
Дон Сайм из MSR предложил добавить поддержку замыканий непосредственно в .NET. Это немного академично, но может помочь немного прояснить ситуацию.