답변:
이 주제에 관한 기사 가 있습니다 . (예제가 많습니다.)
본질적으로 클로저는 나중에 실행될 수있는 코드 블록이지만 처음 작성된 환경을 유지합니다. 즉, 그 이후에도이를 생성 한 메소드의 로컬 변수 등을 계속 사용할 수 있습니다. 메소드 실행이 완료되었습니다.
클로저의 일반적인 기능은 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 자체가 완료된 경우에도 실제로 증가시킬 수 있습니다.
counter
증분 할 수 있습니다. 컴파일러는 counter
필드 를 포함하는 클래스를 생성하며 , 참조하는 모든 코드 counter
는 해당 클래스의 인스턴스를 통과하게됩니다.
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
그것이 다소 도움이되기를 바랍니다.
클로저는 이벤트 나 델리게이트가 정의 될 때와 같이 나중에 호출되거나 실행될 수있는 외부의 변수 (스택에서 변수 아래)를 참조하는 코드 덩어리입니다. ) ... 코드 청크가 참조하는 외부 변수가 범위를 벗어날 수 있고 그렇지 않으면 손실되었을 수 있으므로 코드 청크에 의해 참조된다는 사실 (클로저라고 함)은 런타임에 "hold" "코드의 청크 청크에 의해 더 이상 필요하지 않을 때까지 범위 내의 변수 ...
기본적으로 클로저는 함수에 인수로 전달할 수있는 코드 블록입니다. 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.
나는 그것을 이해하려고 노력해 왔으며, 아래는 Javascript의 동일한 코드와 C #을 보여주는 코드 스 니펫입니다.
자바 스크립트 :
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);
}
}
자바 스크립트 :
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
인라인 익명 메소드 (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"는 루프 와 관련된 개념이 아니라 익명의 메서드 / 람다 식에 대한 로컬 범위 변수 사용-일부 트릭 질문은 그것을 설명하기 위해 루프를 사용합니다.
클로저는 함수 내에서 정의 된 함수로, 부모뿐만 아니라 해당 함수의 로컬 변수에 액세스 할 수 있습니다.
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 메소드에 의해 대리자로 실행되지만 다른 범위에서 모두 함께 수행됩니다.