일부 C # 람다식이 정적 메서드로 컴파일되는 이유는 무엇입니까?


122

아래 코드에서 볼 수 있듯이 Action<>객체를 변수로 선언했습니다 .

이 작업 메서드 대리자가 정적 메서드처럼 작동하는 이유를 알려주세요.

true다음 코드에서 반환 되는 이유는 무엇 입니까?

암호:

public static void Main(string[] args)
{
    Action<string> actionMethod = s => { Console.WriteLine("My Name is " + s); };

    Console.WriteLine(actionMethod.Method.IsStatic);

    Console.Read();
}

산출:

샘플 출력 예

답변:


153

이는 폐쇄가 없기 때문일 가능성이 높습니다. 예를 들면 다음과 같습니다.

int age = 25;
Action<string> withClosure = s => Console.WriteLine("My name is {0} and I am {1} years old", s, age);
Action<string> withoutClosure = s => Console.WriteLine("My name is {0}", s);
Console.WriteLine(withClosure.Method.IsStatic);
Console.WriteLine(withoutClosure.Method.IsStatic);

이 출력됩니다 false에 대한 withClosuretrue대한 withoutClosure.

람다 식을 사용할 때 컴파일러는 메서드를 포함하는 작은 클래스를 생성합니다.이 클래스는 다음과 같이 컴파일됩니다 (실제 구현은 약간 다를 수 있음).

private class <Main>b__0
{
    public int age;
    public void withClosure(string s)
    {
        Console.WriteLine("My name is {0} and I am {1} years old", s, age)
    }
}

private static class <Main>b__1
{
    public static void withoutClosure(string s)
    {
        Console.WriteLine("My name is {0}", s)
    }
}

public static void Main()
{
    var b__0 = new <Main>b__0();
    b__0.age = 25;
    Action<string> withClosure = b__0.withClosure;
    Action<string> withoutClosure = <Main>b__1.withoutClosure;
    Console.WriteLine(withClosure.Method.IsStatic);
    Console.WriteLine(withoutClosure.Method.IsStatic);
}

결과 Action<string>인스턴스가 실제로 이러한 생성 된 클래스의 메서드를 가리키는 것을 볼 수 있습니다 .


4
+1. 확인할 수 있음-클로저없이 static메서드에 대한 완벽한 후보입니다 .
Simon Whitehead 2014 년

3
나는이 질문에 약간의 확장이 필요하다고 제안하려고했는데, 나는 돌아 왔고 거기에있었습니다. 매우 유익합니다. 컴파일러가 내부에서 수행하는 작업을 확인하면 좋습니다.
Liath

4
@Liath Ildasm는 실제로 무슨 일이 일어나고 있는지 이해하는 데 정말 유용합니다. 저는 IL탭 을 사용하여 LINQPad작은 샘플을 검사 하는 경향이 있습니다 .
Lukazoid

@Lukazoid이 컴파일러 출력을 어떻게 얻었는지 알려주시겠습니까? ILDASM은 그러한 출력을 제공하지 않습니다. 어떤 도구 나 소프트웨어로?
nunu 2014 년

8
@nunu이 예제에서는 IL탭을 사용 LINQPad하여 C #을 추론했습니다. 컴파일 된 출력에 해당하는 실제 C #을 가져 오는 몇 가지 옵션 은 컴파일 된 어셈블리 를 사용 ILSpy하거나 Reflector컴파일 된 어셈블리에서 컴파일러 생성 클래스가 아닌 람다를 표시하려는 일부 옵션을 비활성화해야 할 가능성이 높습니다.
Lukazoid

20

"조치 방법"은 구현의 부작용으로 만 정적입니다. 캡처 된 변수가없는 익명 메서드의 경우입니다. 캡처 된 변수가 없기 때문에 메서드에는 일반적으로 로컬 변수에 대한 추가 수명 요구 사항이 없습니다. 다른 지역 변수를 참조했다면 수명이 다른 변수의 수명까지 연장됩니다 ( C # 5.0 사양의 섹션 L.1.7, 지역 변수 및 섹션 N.15.5.1, 캡처 된 외부 변수 참조).

C # 사양은 "익명 클래스"가 아닌 "표현식 트리"로 변환되는 익명 메서드에 대해서만 설명합니다. 식 트리는 예를 들어 Microsoft 컴파일러에서 추가 C # 클래스로 표현 될 수 있지만이 구현은 필요하지 않습니다 (C # 5.0 사양의 섹션 M.5.3에서 확인 됨). 따라서 익명 함수가 정적인지 여부는 정의되지 않습니다. 또한 섹션 K.6은 표현 트리의 세부 사항에 대해 많이 공개합니다.


2
+1 명시된 이유 때문에이 행동은 신뢰할 수 없어야합니다. 구현 세부 사항입니다.
Lukazoid 2014 년

18

Roslyn에서 위임 캐싱 동작이 변경되었습니다. 이전에 언급했듯이 변수를 캡처하지 않은 람다 식은 static호출 사이트에서 메서드 로 컴파일되었습니다 . Roslyn은이 동작을 변경했습니다. 이제 변수를 캡처하거나 캡처하지 않는 모든 람다는 표시 클래스로 변환됩니다.

이 예에서 :

public class C
{
    public void M()
    {
        var x = 5;
        Action<int> action = y => Console.WriteLine(y);
    }
}

네이티브 컴파일러 출력 :

public class C
{
    [CompilerGenerated]
    private static Action<int> CS$<>9__CachedAnonymousMethodDelegate1;
    public void M()
    {
        if (C.CS$<>9__CachedAnonymousMethodDelegate1 == null)
        {
            C.CS$<>9__CachedAnonymousMethodDelegate1 = new Action<int>(C.<M>b__0);
        }
        Action<int> arg_1D_0 = C.CS$<>9__CachedAnonymousMethodDelegate1;
    }
    [CompilerGenerated]
    private static void <M>b__0(int y)
    {
        Console.WriteLine(y);
    }
}

Roslyn :

public class C
{
    [CompilerGenerated]
    private sealed class <>c__DisplayClass0
    {
        public static readonly C.<>c__DisplayClass0 CS$<>9__inst;
        public static Action<int> CS$<>9__CachedAnonymousMethodDelegate2;
        static <>c__DisplayClass0()
        {
            // Note: this type is marked as 'beforefieldinit'.
            C.<>c__DisplayClass0.CS$<>9__inst = new C.<>c__DisplayClass0();
        }
        internal void <M>b__1(int y)
        {
            Console.WriteLine(y);
        }
    }
    public void M()
    {
        Action<int> arg_22_0;
        if (arg_22_0 = C.
                       <>c__DisplayClass0.CS$<>9__CachedAnonymousMethodDelegate2 == null)
        {
            C.<>c__DisplayClass0.CS$<>9__CachedAnonymousMethodDelegate2 =
          new Action<int>(C.<>c__DisplayClass0.CS$<>9__inst.<M>b__1);
        }
    }
}

Roslyn의 위임 캐싱 동작 변경 이 이러한 변경이 이루어진 이유에 대해 설명합니다.


2
감사합니다. 왜 내 Func <int> f = () => 5의 메서드가 정적이 아닌지 궁금합니다.
vc 74


1

이 메서드에는 클로저가 없으며 정적 메서드 자체 (Console.WriteLine)도 참조하므로 정적 일 것으로 예상합니다. 이 메서드는 클로저에 대해 둘러싸는 익명 유형을 선언하지만이 경우에는 필요하지 않습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.