一部の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);

これはfalsewithClosureおよびtrueに対して出力され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、2014

4
@LiathがIldasm、実際に何が起こっているかを理解するために非常に便利ですが、私は使用する傾向があるILのタブをLINQPad少量のサンプルを検査すること。
ルカゾイド2014

@Lukazoidこのコンパイラの出力はどのようにして入手したのですか?ILDASMはそのような出力を提供しません。
nunu 2014

8
@nunuこの例では、C#のILタブを使用してLINQPad推論しました。コンパイルされた出力に対応する実際のC#を取得するためのいくつかのオプションは、コンパイルされたアセンブリを使用するILSpyReflector、またはコンパイラーが生成したクラスではなくラムダを表示しようとするいくつかのオプションを無効にする必要があります。
ルカゾイド2014

20

「アクションメソッド」は、実装の副作用としてのみ静的です。これは、キャプチャされた変数を持たない無名メソッドの場合です。キャプチャされた変数がないため、メソッドには、一般的なローカル変数のライフタイム要件を超える追加のライフタイム要件はありません。他のローカル変数を参照した場合、その存続期間はそれらの他の変数の存続期間まで延長されます(C. 5.0仕様のセクションL.1.7、ローカル変数、およびセクションN.15.5.1、キャプチャされた外部変数を参照)。

C#仕様では、「匿名クラス」ではなく、「式ツリー」に変換される匿名メソッドについてのみ述べていることに注意してください。たとえば、Microsoftコンパイラでは、式ツリーを追加のC#クラスとして表すことができますが、この実装は必要ありません(C#5.0仕様のセクションM.5.3で承認されています)。したがって、無名関数が静的であるかどうかは未定義です。さらに、セクションK.6では、式ツリーの詳細について多くのことが公開されています。


2
+1記載されている理由により、この動作はおそらく信頼されるべきではありません。実装の詳細です。
ルカゾイド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);
    }
}

ロズリン:

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.