programing

.NET에서 '클로저'란 무엇입니까?

new-time 2020. 5. 14. 22:33
반응형

.NET에서 '클로저'란 무엇입니까?


 

폐쇄

란 무엇입니까 ? .NET에 있습니까?

 

그들이 .NET에 존재한다면 그것을 설명하는 코드 스 니펫 (바람직하게는 C #)을 제공 할 수 있습니까?

 

편집 :

Jon Skeet의 기사

통해 클로저가 무엇이고 .NET에서 클로저를 사용하는 방법을 이해했습니다.


 

이 주제에 관한 기사

가 있습니다 . (예제가 많습니다.)본질적으로 클로저는 나중에 실행될 수있는 코드 블록이지만 처음 작성된 환경을 유지합니다. 즉, 그 이후에도이를 생성 한 메소드의 로컬 변수 등을 계속 사용할 수 있습니다. 메소드 실행이 완료되었습니다.클로저의 일반적인 기능은 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에 의해 반환 된 액션은 여전히 ​​카운터 변수에 액세스 할 수 있으며 CreateAction 자체가 완료된 경우에도 실제로 증가시킬 수 있습니다.


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);
    }
}

클로저는 원래 범위에서 변수 값을 유지하는 기능 값입니다. 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

클로저는 생성 된 함수 외부로 전달되는 익명 함수입니다. 사용하는 함수에서 변수를 유지 관리합니다.


다음은 JavaScript에서 비슷한 코드로 만든 C #에 대한 좋은 예입니다.

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 #은 익명 대리자 형식의 폐쇄를 지원합니다.간단한 예는 다음과 같습니다.

 

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);

클로저는 함수가 다른 함수 (또는 메소드) 내에 정의되고

상위 메소드

의 변수를 사용하는 경우입니다 . 메소드에 위치하며 그 안에 정의 된 함수로 랩핑 된 이러한 변수 사용을 클로저 라고

합니다.

Mark Seemann은

블로그 게시물

에서 oop과 함수형 프로그래밍 사이의 병렬 처리를 수행하는 흥미로운 클로저 예제를 보유 하고 있습니다.그리고 더 자세하게

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.

클로저는 이벤트 나 델리게이트가 정의 될 때와 같이 나중에 호출되거나 실행될 수있는 외부의 변수 (스택에서 변수 아래)를 참조하는 코드 덩어리입니다. ) ... 코드 청크가 참조하는 외부 변수가 범위를 벗어날 수 있고 그렇지 않으면 손실되었을 수 있으므로 코드 청크에 의해 참조된다는 사실 (클로저라고 함)은 런타임에 "hold" "코드의 청크 청크에 의해 더 이상 필요하지 않을 때까지 범위 내의 변수 ...


나는 그것을 이해하려고 노력했다. 아래는 Javascript와 C #의 동일한 코드에 대한 코드 스 니펫이 클로저를 보여줍니다.

  1. 카운트 각 이벤트가 발생한 횟수 또는 각 버튼을 클릭 한 횟수는 없습니다.

자바 스크립트 :

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>

씨#:

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. 클릭 이벤트 발생 횟수 총계 또는 제어와 상관없이 클릭 수 총계를 계산합니다.

자바 스크립트 :

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()"/>

씨#:

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에서 간단하고 이해하기 쉬운 대답은 간단합니다.

사전 요구 사항

: 람다 식은 정의 된 메서드 (외부 변수)의 로컬 변수 및 매개 변수를 참조 할 수 있습니다.

    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

If you write an inline anonymous method (C#2) or (preferably) a Lambda expression (C#3+), an actual method is still being created. If that code is using an outer-scope local variable - you still need to pass that variable to the method somehow.

e.g. take this Linq Where clause (which is a simple extension method which passes a lambda expression):

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;
});

if you want to use i in that lambda expression, you have to pass it to that created method.

So the first question that arises is: should it be passed by value or reference?

Pass by reference is (I guess) more preferable as you get read/write access to that variable (and this is what C# does; I guess the team in Microsoft weighed the pros and cons and went with by-reference; According to Jon Skeet's article, Java went with by-value).

But then another question arises: Where to allocate that i?

Should it actually/naturally be allocated on the stack? Well, if you allocate it on the stack and pass it by reference, there can be situations where it outlives it's own stack frame. Take this example:

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;
    });            
}

The lambda expression (in the Where clause) again creates a method which refers to an i. If i is allocated on the stack of Outlive, then by the time you enumerate the whereItems, the i used in the generated method will point to the i of Outlive, i.e. to a place in the stack that is no longer accessible.

Ok, so we need it on the heap then.

So what the C# compiler does to support this inline anonymous/lambda, is use what is called "Closures": It creates a class on the Heap called (rather poorly) DisplayClass which has a field containing the i, and the Function that actually uses it.

Something that would be equivalent to this (you can see the IL generated using ILSpy or ILDASM):

class <>c_DisplayClass1
{
    public int i;

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

It instantiates that class in your local scope, and replaces any code relating to i or the lambda expression with that closure instance. So - anytime you are using the i in your "local scope" code where i was defined, you are actually using that DisplayClass instance field.

So if I would change the "local" i in the main method, it will actually change _DisplayClass.i ;

i.e.

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

it will print out 12, as "i = 10" goes to that dispalyclass field and changes it just before the 2nd enumeration.

A good source on the topic is this Bart De Smet Pluralsight module (requires registration) (also ignore his erroneous use of the term "Hoisting" - what (I think) he means is that the local variable (i.e. i) is changed to refer to the the new DisplayClass field).


In other news, there seems to be some misconception that "Closures" are related to loops - as I understand "Closures" are NOT a concept related to loops, but rather to anonymous methods / lambda expressions use of local scoped variables - although some trick questions use loops to demonstrate it.


A closure is a function, defined within a function, that can access the local variables of it as well as its parent.

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

so the function inside the find method.

 t => t.Name == name

can access the variables inside its scope, t, and the variable name which is in its parents scope. Even though it is executed by the find method as a delegate, from another scope all together.

참고URL : https://stackoverflow.com/questions/428617/what-are-closures-in-net

반응형