C#编译器是否将lambda表达式视为公共或私有方法?

时间:2016-03-07 09:14:19

标签: c# compiler-optimization

在内部,编译器应该将lambda表达式转换为方法。在这种情况下,这些方法是私有的还是公共的(或其他方式),是否可以改变它?

4 个答案:

答案 0 :(得分:57)

这取决于。使用当前版本的Visual Studio,实现lambda的方法永远不会公开,但它们并不总是私有的。一个测试某些lambda版本的简单程序:

public class Program
{
    public static void Main()
    {
        var program = new Program();
        Try("A", program.A);
        Try("B", program.B);
        Try("C", program.C);
        Console.ReadKey();
    }

    private static void Try(string name, Func<Action> generator)
    {
        var mi = generator().Method;
        Console.WriteLine($"{name}: DeclaringType={mi.DeclaringType}, Attributes={mi.Attributes}");
    }

    private Action A() => () => { };
    private Action B() => () => { ToString(); };
    private Action C()
    {
        var c = 1;
        return () => c.ToString();
    }
}

打印

A: DeclaringType=Scratch.Program+<>c, Attributes=PrivateScope, Assembly, HideBySig
B: DeclaringType=Scratch.Program, Attributes=PrivateScope, Private, HideBySig
C: DeclaringType=Scratch.Program+<>c__DisplayClass4_0, Attributes=PrivateScope, Assembly, HideBySig

A的lambda没有任何捕获。它是作为空闭包类的internal方法创建的。

B的lambda捕获this。它是作为包含类的private方法创建的。

C的lambda捕获c。它是作为非空闭包类的internal方法创建的。

所有这些都是无证的,并且在过去已经发生了变化,所以最好不要依赖它。重要的是,当您调用匿名方法时,它的行为与指定的一样。如果您需要更多内容,则不应使用匿名方法。根据您所使用的内容,您可能仍然可以使用lambdas,但使用表达式树,或者您可能需要创建常规命名方法。

答案 1 :(得分:25)

  

在内部,编译器应该将lambda表达式转换为方法。

我假设&#34; lambda&#34;你的意思是lambda转换为委托类型。转换为表达式树类型的Lambdas肯定不会作为方法生成。

编译器确实将这样的lambda转换为方法,是的。没有要求它会这样做,但这样做很方便。

  

在这种情况下,这些方法是私有的还是公共的(或其他方式),是否可以改变它?

这个问题有点不连贯。假设我告诉你lambda是一种公共方法。它没有可从C#访问的名称;你会如何利用其公共利益?辅助功能修饰符适用于具有名称的成员。可访问性域的概念在名称解析期间提供名称的域。

当然,在实践中,编译器必须为不可赎回方法的元数据生成一些可访问性位。在闭包类上生成的方法是内部的,因为这是使用它们可以验证的最方便的方法。没有闭包生成的方法可以是私有的。

同样,这些都不是必需的,所有这些都是实施细节,可能会有所变化。您不应该试图利用编译器的代码生成细节。

答案 2 :(得分:7)

来自CLR,来自Jeffrey Richter的C#书

  

编译器自动在类

中定义一个新的私有方法      

...编译器会自动为您创建方法名称

     

...编译器生成的匿名方法总是会结束   是私有的,该方法要么是静态的,要么是非静态的   该方法是否访问任何实例成员

因此该方法被声明为privateinternal

例如代码

class AClass {
    public void SomeMethod() {
        Action lambda = () => Console.WriteLine("Hello World");
        lambda();
    }
}

将生成IL声明为

.field private static class [mscorlib]System.Action 'CS$<>9__CachedAnonymousMethodDelegate1'

正如您所见,它是private static字段。

但是请注意,如果将示例更改为

,则可以优化lambda表达式
class AClass
{
    string a = "Hello World";

    public void SomeMethod()
    {
        Action lambda = () => Console.WriteLine(a);
        lambda();
    }
}

编译器将优化它,并且根本不会有lambda声明

IL_0001:  ldstr      "Hello World"
IL_0006:  call       void [mscorlib]System.Console::WriteLine(string)

答案 3 :(得分:2)

正如@hvd提到的那样,lambda表达式使用其周围环境中的参数(闭包大小写)之间存在差异。请参阅:Why do some C# lambda expressions compile to static methods?

因此,当lambda表达式可以转换为委托包装器而没有任何外部依赖时,这个问题只对非闭包情况有意义。

您可以传递该生成的类(基本上包装一个委托),它将始终引用定义程序集中生成的委托。因此,如果引用程序集,您可以从任何位置调用它。

刚刚验证了传递和执行另一个程序集中定义的Action是有效的,尽管Action.Method本身已标记为内部。

// Main, first assembly
namespace ConsoleApplication1
{
    public class B : IB
    {
        Action _action;
        public void AddAction(Action act)
        {
            _action = act;
        }

        public void Invoke()
        {
            Console.WriteLine(_action.Target);
            Console.WriteLine("Is public: {0}", _action.Method.IsPublic);
            _action();
        }

    }

    class Program
    {
        static void Main(string[] args)
        {
            var a = new A();
            var b = new B();
            a.AddActionTo(b);
            b.Invoke();

            Console.ReadKey();
        }
    }
}

在其他集会中:

namespace OtherAssembly
{
    public interface IB
    {
        void AddAction(Action act);
    }

    public class A
    {
        public void AddActionTo(IB b)
        {
            Action act = () => { };
            b.AddAction(act);
        }
    }
}