반응형
다음의 예는 클로저에서 캡처된 변수를 수정했을 때의 상황을 보여주기 위한 예이다.
var index = 0;
Func<IEnumerable<int>> sequence = () => Utilities.Generate(30, () => index++);
index = 20;
foreach(int n in sequence())
WriteLine(n);
WriteLine("Done");
index = 100;
foreach(var n in sequence())
WriteLine(n);
위의 코드를 실행하면 20부터 50까지를 출력한 후, 100부터 130까지를 출력한다.
C# 컴파일러는 쿼리 표현식을 실행 코드로 변활할 때 다양한 작업을 수행한다.
사용된 표현식이 어떤 형태인지에 따라 생성 방법이 다르다.
예시A (인스턴스 변수 접근 X, 지역변수 접근 X)
int[] someNumbers = {0, 1, 2, 3, 4, 5};
var answers = from n in someNumbers
select n * n;
별다른 변수 참조가 없기 대문에 델리게이트를 통해 참조할 정적 메서드를 생성한다.
private static int HiddenFunc(int n) => (n * n);
private static Func<int, int> HiddenDelegateDefinition;
int[] someNumbers = new int[]{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
if(HiddenDelegateDefinition == null)
{
HiddenDelegateDefinition = new Func<int, int>(HiddenFunc);
}
var answers = someNumbers.Select<int, int>(HiddenDelegateDefinition);
예시B (인스턴스 변수에 접근O, 지역변수에 접근 X)
public class ModeFilter
{
private readonly int modulus;
public ModeFilter(int mod)
{
modulus = mod;
}
public IEnumerable<int> FindVlaues(
IEnumerable<int> sequence)
{
return from n in sequence
where n % modulus == 0
select n * n;
}
}
인스턴스 변수를 참조하기 위해 델리게이트를 통해 참조하는 인스턴스 메서드르 생성한다.
public class ModeFilter
{
private readonly int modulus;
// 새롭게 추가된 메서드
private bool WhereClause(int n) => ((n % this.modulus) == 0);
// 기존 메서드
private static int SelectClause(int n) => (n * n);
// 기존 델리게이트
private static Func<int, int> SelectDelegate;
public IEnumerable<int> FindValues(IEnumerable<int> sequence)
{
if(SelectDelegate == null)
{
SelectDelegate = new Func<int, int>(SelectClause);
}
return sequence.Where<int>(
new Func<int, bool>(this.WhereClause)).
Select<int, int>(SelectClause);
}
}
예시C (인스턴스 변수에 접근X, 지역변수에 접근O)
public class ModFilter
{
private readonly int modulus;
public ModFilter(int mod)
{
modulus = mod;
}
public IEnumerable<int> FindValues(IEnumerable<int> sequence)
{
int numValue = 0;
return from n in sequence
where n % modulus == 0
select n * n / ++ numValues;
}
}
지역변수 혹은 메서드의 매개변수를 사용하는 코드가 포함될 경우 컴파일러가 상당한 추가 작업을 수행하게 된다.
이 경우 클로저가 필요하기 때문에 private로 중첩 클래스를 선언한다.
public class ModFilter
{
private sealed class Closure
{
public ModFilter outer;
public int numValues;
public int SelectClause(int n) => ((n * n) / this.numValues);
}
private readonly int modulus;
public ModFilter(int mod)
{
this.modulus = mod;
}
private bool WhereClause(int n) => ((n % this.modulus) == 0);
public IEnumerable<int> FindValues(IEnumerable<int> sequence)
{
var c = new Closure();
c.outer = this;
c.numValues = 0;
return sequence.Where<int>(
new Func<int, bool>(this.WhereClause))
.Select<int, int>(new Func<int, int>(c.SelectClause));
}
}
람다 표현식에서 사용하는 모든 지역변수를 포함하는 중첩 클래스를 생성하는 것을 알 수 있다.
따라서 람다 내부에서든, 람다 외부에서든 와전히 동일한 필드에 접근하게 된다.
반응형
'C# > Effective C#' 카테고리의 다른 글
[Effectvie C#] Item 46 리소스 정리를 위해 using과 try/finally를 활용하라 (0) | 2023.06.12 |
---|---|
[Effectvie C#] Item 45 메서드가 실패했음을 알리기 위해서 예외를 이용하라 (0) | 2023.05.23 |
[Effective C#] Item 43 쿼리 결과의 의미를 명확히 강제하고, Single()과 First()를 사용하라. (0) | 2023.05.11 |
[Effective C#] Item 42 IEumerable<T> 데이터 소스와 IQueryable 데이터 소스를 구분하라 (0) | 2023.05.11 |
[Effective C#] Item 40 지연 수행과 즉시 수행을 구분하라 (0) | 2023.05.02 |