以下のコードでわかるように、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();
}
これは、たとえば次のようなクロージャーがないためです。
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
に対してwithClosure
を、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>
インスタンスは、これらの生成されたクラスのメソッドを実際に指していることがわかります。
「アクションメソッド」は、実装の副作用としてのみ静的です。これは、キャプチャされた変数のない匿名メソッドの場合です。キャプチャされた変数がないため、メソッドには一般的なローカル変数の場合を超える追加のライフタイム要件はありません。他のローカル変数を参照した場合、そのライフタイムはそれらの他の変数のライフタイムまで延長されます(L.1.7、ローカル変数、およびN.15.5.1、キャプチャ済みを参照)外部変数、C#5.0仕様)。
C#仕様は、「匿名クラス」ではなく、「式ツリー」に変換される匿名メソッドについてのみ言及していることに注意してください。たとえば、Microsoftコンパイラでは、式ツリーを追加のC#クラスとして表すことができますが、この実装は必須ではありません(C#5.0仕様のM.5.3で認められています)。したがって、匿名関数が静的であるかどうかは未定義です。さらに、セクションK.6では、式ツリーの詳細に関して多くの情報が公開されています。
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のデリゲートキャッシュ動作の変更 この変更が行われた理由について説明します。
このメソッドにはクロージャーがなく、静的メソッド自体(Console.WriteLine)も参照するため、静的であると予想されます。このメソッドは、クロージャーを囲む匿名型を宣言しますが、この例では必須ではありません。
C#6以降、これは常にデフォルトのインスタンスメソッドになり、静的になることはありません(したがってactionMethod.Method.IsStatic
は常にfalseです。
こちらをご覧ください: C#5の静的からC#6のインスタンスメソッドにキャプチャが変更されていないラムダがあるのはなぜですか
そしてここ: CSCとRoslynコンパイラの静的ラムダ式評価の違い?