为什么某些C#lambda表达式可编译为静态方法?


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();
}

输出:

样本输出示例

Answers:


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);

这将输出falsewithClosuretruewithoutClosure

当您使用lambda表达式时,编译器会创建一个小类来包含您的方法,这将编译为如下所示的内容(实际实现可能会略有不同):

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方法的理想选择。
西蒙·怀特海德2014年

3
我只是建议这个问题需要扩展,我回来了。非常有用的信息-很高兴看到编译器在做什么。
Liath 2014年

4
@Liath Ildasm对于了解实际情况非常有用,我倾向于使用的IL选项卡LINQPad检查小样本。
2014年

@Lukazoid您能否告诉我们您是如何获得此编译器输出的?ILDASM不会提供此类输出。通过任何工具或软件?
2014年

8
@nunu在此示例中,我使用的IL制表符LINQPad并推断出C#。要获得与已编译输出实际等效的C#的某些选项,将使用ILSpyReflector在已编译的程序集上使用,您很可能需要禁用某些选项,这些选项将尝试显示lambda而不是编译器生成的类。
2014年

20

“动作方法”仅作为实现的副作用是静态的。这是没有捕获变量的匿名方法的情况。由于没有捕获的变量,因此该方法除了一般的局部变量之外,没有其他寿命要求。如果它确实引用了其他局部变量,则其生存期将扩展到那些其他变量的生存期(请参阅C#5.0规范中的L.1.7节和Local N.15.5.1节,捕获的外部变量)。

请注意,C#规范仅讨论将匿名方法转换为“表达式树”,而不是“匿名类”。虽然表达式树可以表示为其他C#类,例如,在Microsoft编译器中,但不需要此实现(如C#5.0规范中的M.5.3节所确认)。因此,匿名函数是否静态是不确定的。此外,第K.6节在表达式树的细节方面还有很多空缺。


2
出于上述原因,最有可能不应该+1这种行为;这是一个非常详细的实现细节。
Lukazoid 2014年

18

代理缓存行为已在Roslyn中更改。如前所述,任何未捕获变量的lambda表达式都static在调用站点处编译为方法。罗斯林改变了这种行为。现在,任何捕获或不捕获变量的lambda都将转换为显示类:

给出以下示例:

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),因此我希望它是静态的。该方法将为封闭声明一个封闭的匿名类型,但是在这种情况下,它不是必需的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.