C# - CachedAnonymousMethodDelegate 유형의 코드 생성
C# 문법이 발전하면서 컴파일러가 자동 생성해 주는 코드가 많아졌습니다. 그런데, 가만 보면 이런 것에도 패턴이 있는 것 같습니다. 가령, CachedAnonymousMethodDelegate라는 유형이 있는데요.
SynthesizedLocalKind
; http://source.roslyn.io/#Microsoft.CodeAnalysis/SynthesizedLocalKind.cs,c465228f29a95c51,references
///
/// Local variable used to cache a delegate that is used in inner block (possibly a loop),
/// and can be reused for all iterations of the loop.
///
CachedAnonymousMethodDelegate = 31,
CachedAnonymousMethodDelegate 유형의 로컬 변수를 생성하려면 다음과 같이 예제 구성을 하면 됩니다.
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
DoMethod();
}
private static void DoMethod()
{
Func<string, bool> func3 = (arg) =>
{
return true;
};
List<string> list = new List<string>();
var list2 = list.Where(func3);
}
}
}
Visual Studio 2013에서 위의 소스 코드를 컴파일하고 .NET Reflector에서 "C# - None" 옵션으로 보면 다음과 같이 DoMethod 내부에 코딩한 람다 함수가 "CS$<>9__
CachedAnonymousMethodDelegate1" 이름의 멤버 필드로 Program 클래스에 정의되면서 캐시 역할의 변수를 합니다. 또한 람다 함수의 코드는 "<DoMethod>b__0"라는 멤버 메서드로 자동 생성되고!
internal class Program
{
// Fields
[CompilerGenerated]
private static Func<string, bool> CS$<>9__CachedAnonymousMethodDelegate1;
// Methods
public Program()
{
base..ctor();
return;
}
[CompilerGenerated]
private static bool <DoMethod>b__0(string arg)
{
bool flag;
flag = 1;
Label_0005:
return flag;
}
private static void DoMethod()
{
Func<string, bool> func;
List<string> list;
IEnumerable<string> enumerable;
if (CS$<>9__CachedAnonymousMethodDelegate1 != null)
{
goto Label_001B;
}
CS$<>9__CachedAnonymousMethodDelegate1 = new Func<string, bool>(null, <DoMethod>b__0);
Label_001B:
func = CS$<>9__CachedAnonymousMethodDelegate1;
list = new List<string>();
enumerable = Enumerable.Where<string>(list, func);
return;
}
private static void Main(string[] args)
{
DoMethod();
return;
}
}
재미있는 것은, cache 변수의 역할을 하는 멤버 필드의 이름에 SynthesizedLocalKind::CachedAnonymousMethodDelegate 상숫값의 이름이 반영되어 있다는 것입니다. 게다가 CS$...에 붙는 번호들 같은 경우에도 그냥 붙는 것이 아니라고 합니다. 이에 대해서는 아래의 글을 (재미 삼아) 보시면 될 것 같습니다. ^^
Where to learn about VS debugger 'magic names'
; http://stackoverflow.com/questions/2508828/where-to-learn-about-vs-debugger-magic-names
그런데 역시나 이런 내부적인 규칙들은 public이 아니라는 점에 주의해야 합니다. 실제로 Visual Studio 2015에서 위의 소스 코드를 빌드하면 다음과 같이 전혀 다른 결과를 얻게 됩니다.
internal class Program
{
// Methods
public Program()
{
base..ctor();
return;
}
private static void DoMethod()
{
Func<string, bool> func;
List<string> list;
IEnumerable<string> enumerable;
Label_0020:
func = <>c.<>9__1_0 ?? (<>c.<>9__1_0 = new Func<string, bool>(<>c.<>9, this.<DoMethod>b__1_0));
list = new List<string>();
enumerable = Enumerable.Where<string>(list, func);
return;
}
private static void Main(string[] args)
{
DoMethod();
return;
}
[Serializable, CompilerGenerated]
private sealed class <>c
{
// Fields
public static readonly Program.<>c <>9;
public static Func<string, bool> <>9__1_0;
// Methods
static <>c()
{
<>9 = new Program.<>c();
return;
}
public <>c()
{
base..ctor();
return;
}
internal bool <DoMethod>b__1_0(string arg)
{
bool flag;
flag = 1;
Label_0005:
return flag;
}
}
}
즉, CachedAnonymousMethodDelegate 접미사가 붙은 멤버 필드 대신 별도의 임시 클래스(<>c)가 만들어지고 그 안에 필드와 메서드의 본체가 정의됩니다.
이런 차이가 있기 때문에 람다 함수 안에서 다음과 같은 타입 의존적인 코드를 하게 되면,
class Program
{
static void Main(string[] args)
{
Program pg = new Program();
pg.DoMethod();
}
private void DoMethod()
{
Func<string, bool> func3 = (arg) =>
{
StackFrame st = new StackFrame();
Console.WriteLine(st.GetMethod().DeclaringType.Name);
Console.WriteLine(st.GetMethod().DeclaringType.FullName);
return true;
};
List<string> list = new List<string>();
list.Add("TEST");
var list2 = list.Where(func3);
list2.ToList();
}
}
Visual Studio 2013에서는 다음과 같이 출력이 되고,
Type.Name: Program
Type.FullName: ConsoleApp1.Program
Visual Studio 2015 이후로는 이런 결과가 나옵니다.
Type.Name: <>c
Type.FullName: ConsoleApp1.Program+<>c
재미있는 것은, Visual Studio 2013에서도 람다 함수 내에 변수를 capture 하게 되면,
public string DoMethod()
{
StringBuilder sb = new StringBuilder();
Func<string, bool> func3 = (arg) =>
{
sb.AppendLine(arg);
return true;
};
List<string> list = new List<string>();
list.Add("TEST");
var list2 = list.Where(func3);
list2.ToList();
}
2015에서와 같은 결과가 나옵니다.
Visual Studio 2013
Type.Name: <>c
Type.FullName: ConsoleApp1.Program+<>c
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]