【问题标题】:Why do some C# lambda expressions compile to static methods?为什么某些 C# lambda 表达式编译为静态方法?
【发布时间】:2014-10-25 13:43:49
【问题描述】:

正如您在下面的代码中看到的,我已将 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();
}

输出:

【问题讨论】:

    标签: c# .net reflection lambda


    【解决方案1】:

    这很可能是因为没有闭包,例如:

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

    这将为withClosure 输出false,为withoutClosure 输出true

    当您使用 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&lt;string&gt; 实例实际上指向这些生成的类上的方法。

    【讨论】:

    • +1。可以确认 - 没有关闭它们是 static 方法的完美候选者。
    • 我只是建议这个问题需要一些扩展,我回来了,它就在那里。信息量很大 - 很高兴看到编译器在幕后做了什么。
    • @Liath Ildasm 对于了解实际情况非常有用,我倾向于使用LINQPadIL 标签来检查小样本。
    • @Lukazoid 请告诉我们您是如何获得此编译器输出的? ILDASM 不会给出这样的输出.. 通过任何工具或软件?
    • @nunu 在此示例中,我使用了LINQPadIL 选项卡并推断出C#。获得编译输出的实际 C# 等效项的一些选项是在编译的程序集中使用 ILSpyReflector,您很可能需要禁用一些选项,这些选项将尝试显示 lambdas 而不是编译器生成的类.
    【解决方案2】:

    “动作方法”是静态的,只是作为实现的副作用。这是一个没有捕获变量的匿名方法的情况。由于没有捕获的变量,该方法除了一般的局部变量之外没有额外的生命周期要求。如果它确实引用了其他局部变量,则它的生命周期会延伸到那些其他变量的生命周期(参见第 L.1.7 节,局部变量和第 N.15.5.1 节,捕获的外部变量变量,在 C# 5.0 规范中)。

    请注意,C# 规范仅讨论将匿名方法转换为“表达式树”,而不是“匿名类”。虽然表达式树可以表示为附加的 C# 类,例如,在 Microsoft 编译器中,但不需要此实现(正如 C# 5.0 规范中的第 M.5.3 节所承认的那样)。因此,匿名函数是否为静态是未定义的。此外,第 K.6 节对表达式树的细节留有很大的余地。

    【讨论】:

    • +1 由于所述原因,很可能不应依赖此行为;这在很大程度上是一个实现细节。
    【解决方案3】:

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

    Delegate caching behavior changes in Roslyn 讲述了为什么要进行此更改。

    【讨论】:

    • 谢谢,我想知道为什么我的 Func f = () => 5 的方法不是静态的
    【解决方案4】:

    从 C# 6 开始,这将始终默认为实例方法,并且永远不会是静态的(因此 actionMethod.Method.IsStatic 将始终为 false)。

    请看这里:Why has a lambda with no capture changed from a static in C# 5 to an instance method in C# 6?

    这里:Difference in CSC and Roslyn compiler's static lambda expression evaluation?

    【讨论】:

      【解决方案5】:

      该方法没有闭包,并且还引用了一个静态方法本身(Console.WriteLine),所以我希望它是静态的。该方法将为闭包声明一个封闭的匿名类型,但在这种情况下,它不是必需的。

      【讨论】:

        猜你喜欢
        • 2015-07-12
        • 2017-10-30
        • 1970-01-01
        • 1970-01-01
        • 2016-06-20
        • 2015-06-07
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多