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


195

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

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

답변:


258

이 주제에 관한 기사 가 있습니다 . (예제가 많습니다.)

본질적으로 클로저는 나중에 실행될 수있는 코드 블록이지만 처음 작성된 환경을 유지합니다. 즉, 그 이후에도이를 생성 한 메소드의 로컬 변수 등을 계속 사용할 수 있습니다. 메소드 실행이 완료되었습니다.

클로저의 일반적인 기능은 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 자체가 완료된 경우에도 실제로 증가시킬 수 있습니다.


57
고마워 존. BTW, .NET에서 모르는 것이 있습니까? :) 질문이 있으시면 누구에게 가시나요?
개발자

44
항상 배울 것이 더 많습니다.) 방금 C #을 통해 CLR을 읽었습니다. 매우 유익합니다. 그 외에는 보통 Marc Gravell에게 WCF / 바인딩 / 식 나무를 요구하고 Eric Lippert에게 C # 언어를 요구합니다.
Jon Skeet

2
나는 그것을 알아 차 렸지만 여전히 "나중에 실행될 수있는 코드 블록"이라는 것에 대한 당신의 진술은 단순히 잘못되었다고 생각합니다. 실행과 관련이 없으며 변수 값과 범위와 관련이 있습니다. 그 자체.
Jason Bunting

11
폐쇄를 실행할 수 없다면 클로저는 유용하지 않으며, "나중에"는 환경을 포착 할 수있는 "홀수 성"(실행 시간에 의해 사라 졌을 수도 있음)을 강조합니다. 문장의 반만 인용 하면 불완전한 대답입니다.
Jon Skeet

4
@SLC : 그렇습니다. counter증분 할 수 있습니다. 컴파일러는 counter필드 를 포함하는 클래스를 생성하며 , 참조하는 모든 코드 counter는 해당 클래스의 인스턴스를 통과하게됩니다.
Jon Skeet 2016 년

22

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

안녕하세요, Daniil-당신의 답변은 매우 유용하며 귀하의 답변을 넘어서서 후속 조치를 취하고 싶지만 링크가 끊어졌습니다. 불행히도 내 googlefu는 어디로 옮겼는지 알기에 충분하지 않습니다.
녹스

11

클로저는 원래 범위에서 변수 값을 유지하는 기능 값입니다. 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는 주로 클로저를 사용하여 구축됩니다. 대부분의 개발자에게 가장 편리한 방법은 동적으로 생성 된 컨트롤에 이벤트 처리기를 추가하는 것입니다. 다른 곳에 데이터를 저장하지 않고 컨트롤을 인스턴스화 할 때 클로저를 사용하여 동작을 추가 할 수 있습니다.


10
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

다음은 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

그것이 다소 도움이되기를 바랍니다.


1
예를 들었지만 일반적인 정의는 제공하지 않았습니다. 나는 당신의 의견에서 그것들이 '범위에 관한 것'이라고 말하지만 분명히 그보다 더 많은 것이 있습니까?
ladenedge

2

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


다른 사람의 설명에서 알 수 있듯이 기술적으로는 싫어하지만 클로저는 범위와 더 관련이 있습니다. 클로저는 몇 가지 다른 방식으로 만들 수 있지만 클로저는 수단이 아닙니다.
Jason Bunting

1
클로저는 비교적 새로운 것이기 때문에 오해의 가능성이 있지만 범위 부분을 얻습니다. 답변은 범위를 중심으로합니다. 그래서 yr 주석이 수정하려고하는 것이 누락되었습니다 ... 스코프와 관련이 있지만 코드 덩어리는 무엇입니까? (기능, 익명의 방법 등)
Charles Bretana

일부 "실행 가능한 코드 청크 (chunk of runnable code)"가 해당 변수가 일반적으로 "범위를 벗어 났거나"파괴 된 후 범위 내에서 구문 적으로 "외부"인 변수 또는 메모리 내 값에 액세스 할 수 있다는 종결의 열쇠가 아님 ?
Charles Bretana

그리고 @Jason, 기술적 인 것에 대해 걱정할 필요가 없습니다.이 폐쇄 아이디어는 동료와의 긴 토론에서 자바 스크립트 폐쇄에 관한 오랜 토론에서 머리를 감싸기 위해 오랜 시간이 걸렸습니다 ...하지만 그는 Lisp nut 였고 결코 결코 그의 설명에서 추상화를 상당히
습득했습니다

2

기본적으로 클로저는 함수에 인수로 전달할 수있는 코드 블록입니다. 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
나는 기술적으로 싫어하지만 클로저는 범위와 더 관련이 있습니다. 클로저는 몇 가지 다른 방식으로 만들 수 있지만 클로저는 수단이 아닙니다.
Jason Bunting

2

클로저는 함수가 다른 함수 (또는 메소드) 내에서 정의되고 상위 메소드 의 변수를 사용하는 경우입니다 . 메소드에 위치하며 그 안에 정의 된 함수로 랩핑 된 이러한 변수 사용을 클로저 라고 합니다.

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.

0

나는 그것을 이해하려고 노력해 왔으며, 아래는 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);
    }
}

0

책 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

0

인라인 익명 메소드 (C # 2) 또는 (바람직하게는) Lambda 표현식 (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 팀은 장단점을 가늠하고 참조 기준을 따랐습니다 .Jon Skeet 's에 따르면 는 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는 Outlive의 i, 즉 더 이상 액세스 할 수없는 스택의 위치를 ​​가리 킵니다.

좋아, 그래서 우리는 힙에 필요합니다.

따라서 C # 컴파일러는이 인라인 익명 / 람다를 지원하기 위해 " 클로저 " 라고하는 것을 사용합니다 .이 클래스 는 i를 포함하는 필드가있는 힙에 클래스를 생성합니다 ( 실제로 좋지 않은 ). 그것.

이것과 동등한 것 (ILSpy 또는 ILDASM을 사용하여 생성 된 IL을 볼 수 있음) :

class <>c_DisplayClass1
{
    public int i;

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

로컬 범위에서 해당 클래스를 인스턴스화하고 i 또는 람다 식과 관련된 모든 코드를 해당 클로저 인스턴스로 바꿉니다. 따라서-정의 된 "로컬 범위"코드에서 i를 사용하는 경우 실제로 해당 DisplayClass 인스턴스 필드를 사용하는 것입니다.

메인 메소드에서 "local"i를 변경하면 실제로 _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

"i = 10"은 해당 dispalyclass 필드로 이동하여 2 번째 열거 직전에 변경하므로 12를 인쇄합니다.

주제에 대한 좋은 소스는이 Bart De Smet Pluralsight 모듈 (등록 필요)입니다 ( "게양"이라는 용어를 잘못 사용하는 것을 무시하십시오)-로컬 변수 (즉, i)가 새 DisplayClass 필드로 이동).


다른 소식으로, "Closures"가 루프와 관련이 있다는 오해가 있습니다. "Closures"는 루프 와 관련된 개념이 아니라 익명의 메서드 / 람다 식에 대한 로컬 범위 변수 사용-일부 트릭 질문은 그것을 설명하기 위해 루프를 사용합니다.


-1

클로저는 함수 내에서 정의 된 함수로, 부모뿐만 아니라 해당 함수의 로컬 변수에 액세스 할 수 있습니다.

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 메소드에 의해 대리자로 실행되지만 다른 범위에서 모두 함께 수행됩니다.


2
클로저는 그 자체가 함수가 아니며 함수보다 범위에 대해 이야기함으로써 더 정의됩니다. 함수는 범위를 유지하는 데 도움이되므로 클로저가 생성됩니다. 그러나 폐쇄는 기능적으로 기술적으로 올바르지 않습니다. nitpick에 죄송합니다. :)
Jason Bunting
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.