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

Ale*_* Sk 47 c# compiler-optimization

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

小智 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

版画

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
Run Code Online (Sandbox Code Playgroud)

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

B的lambda抓住了this.它是作为private包含类的方法创建的.

C的lambda抓住了c.它是作为internal非空闭包类的方法创建的.

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

  • @ haim770包含匿名方法的方法需要能够访问该方法才能构造委托.如果匿名方法是作为同一个类的成员创建的,那么它可以是"private",因为方法可以访问它们自己的类的私有方法.如果匿名方法是作为不同类的成员创建的,则它至少需要为"internal",因为方法不能访问其他类的私有方法.提高可见度(使所有东西都是内部的)是可能的,但是没有理由让它比需要更明显. (5认同)

Eri*_*ert 25

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

我假设"lambda"是指lambda转换为委托类型.转换为表达式树类型的Lambdas肯定不会作为方法生成.

事实上编译器确实将这样的lambda变成了方法,是的.没有要求它这样做,但这样做很方便.

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

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

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

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


Nik*_*yev 7

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

编译器自动在类中定义一个新的私有方法

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

...编译器生成的匿名方法总是最终是私有的,并且该方法是静态的或非静态的,具体取决于该方法是否访问任何实例成员

所以该方法被声明为privateinternal.

例如代码

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

将产生IL声明为

.field private static class [mscorlib]System.Action 'CS$<>9__CachedAnonymousMethodDelegate1'
Run Code Online (Sandbox Code Playgroud)

你可以看到它是private static场.

但是请注意,如果将示例更改为,则可以优化lambda表达式

class AClass
{
    string a = "Hello World";

    public void SomeMethod()
    {
        Action lambda = () => Console.WriteLine(a);
        lambda();
    }
}
Run Code Online (Sandbox Code Playgroud)

编译器会优化它,根本就没有lambda声明

IL_0001:  ldstr      "Hello World"
IL_0006:  call       void [mscorlib]System.Console::WriteLine(string)
Run Code Online (Sandbox Code Playgroud)

  • 从VS2015开始,此信息不再准确.作为优化,新编译器避免创建静态方法,因为非静态方法在通过委托调用时提供小的性能提升. (6认同)
  • @hvd我总是想知道人们怎么知道这样的事情 (5认同)
  • @AlexanderDerck https://github.com/dotnet/roslyn/blob/master/docs/compilers/CSharp/CodeGen%20Differences.md (5认同)
  • @AlexanderDerck有关开发人员在Roslyn问题跟踪器中提供的内容和原因的详细信息. (2认同)