Что такое in.NET закрытий?

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


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


EDIT: я прошел статья Джона Скита чтобы понять, что такое закрытие и как их использовать .Сеть.

12 ответов


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

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

общая особенность замыканий реализована в 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 сам закончил.


Если вы заинтересованы в том, как C# реализует закрытие читать "Я знаю ответ (его 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);
    }
}

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

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

    delegate int testDel();

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

    }

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

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


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

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


вот пример для 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

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


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

вот простой пример:
Список.Метод 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);

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

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

и сделать его более подробно

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.

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


Я тоже пытался понять это, ниже приведены фрагменты кода для того же кода в Javascript и C#, показывающие закрытие.

  1. Count нет раз каждое событие произошло или нет раз каждая кнопка нажата.

JavaScript:

var c = function ()
{
    var d = 0;

    function inner() {
      d++;
      alert(d);
  }

  return inner;
};

var a = c();
var b = c();

<body>
<input type=button value=call onClick="a()"/>
  <input type=button value=call onClick="b()"/>
</body>

C#:

using System.IO;
using System;

class Program
{
    static void Main()
    {
      var a = new a();
      var b = new a();

       a.call();
       a.call();
       a.call();

       b.call();
       b.call();
       b.call();
    }
}

public class a {

    int b = 0;

    public  void call()
    {
      b++;
     Console.WriteLine(b);
    }
}
  1. подсчет общего нет раз нажмите событие произошло или подсчет общего нет кликов независимо от управление.

JavaScript:

var c = function ()
{
    var d = 0;

    function inner() {
     d++;
     alert(d);
  }

  return inner;
};

var a = c();

<input type=button value=call onClick="a()"/>
  <input type=button value=call onClick="a()"/>

C#:

using System.IO;
using System;

class Program
{
    static void Main()
    {
      var a = new a();
      var b = new a();

       a.call();
       a.call();
       a.call();

       b.call();
       b.call();
       b.call();
    }
}

public class a {

    static int b = 0;

    public void call()
    {
      b++;
     Console.WriteLine(b);
    }
}

просто из ниоткуда, простой и более понятный ответ из книги C# 7.0 nutshell.

Pre-requisit вы должны знать :лямбда-выражения могут ссылаться на локальные переменные и параметры метода в которой он определен (внешние переменные).

    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

если вы пишете встроенный анонимный метод (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 пошел с по-значению).

но тут возникает другой вопрос:где выделить, что я?

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

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# делает для поддержки этого встроенного анонимного / лямбда, это использовать то, что называется "закрытие": он создает класс в куче под названием (плохо) 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.

поэтому, если я изменю "локальный" i в основном методе, он фактически изменит _DisplayClass.я ;

т. е.

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 и изменяет его непосредственно перед 2-м перечислением.

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


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


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

public string GetByName(string name)
{
   List<things> theThings = new List<things>();
  return  theThings.Find<things>(t => t.Name == name)[0];
}

таким образом, функция внутри метода find.

 t => t.Name == name

может получить доступ к переменным внутри его области, t и имени переменной, которая находится в области родителей. Несмотря на то, что он выполняется методом find как делегат, из другой области все вместе.