2016-03-07 48 views
47

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

+0

afaik编译器创建一个包含此lambda作为方法的整个类。所以为了能够称呼它,它至少应该是“内部”的。但我不是编译器专家。 –

+0

@RenéVogt这取决于lambda是否捕获任何东西。如果没有,封闭课程就不需要了。 – hvd

+3

如果它是公开的,你会怎样称呼它?它没有任何人知道的名字,除了编译器。除了包含类以外,任何人都不会使用它,所以没有理由使它成为私有的。 –

回答

58

这取决于。使用当前版本的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的拉姆达没有任何捕获。它创建为空闭包类的internal方法。

B's lambda captures this。它创建为包含类的private方法。

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

所有这些都是无证的,并且在过去发生了变化,所以避免依赖它是件好事。重要的是,当你调用匿名方法,它的行为如指定。如果你需要的不仅仅是这些,你不应该使用匿名方法。根据你以后的情况,你可能仍然可以使用lambda表达式,但是使用表达式树,或者你可能需要创建常规的命名方法。

+0

很好的答案。但是,捕获与方法的可见性有什么关系?如果'A'设置为'private'(或'B'设置为'internal'),会出现什么问题? – haim770

+5

@ haim770包含匿名方法的方法需要能够访问该方法以构建委托。如果匿名方法是作为同一个类的成员创建的,那么它可以是'private',因为方法可以访问他们自己类的私有方法。如果匿名方法是作为不同类的成员创建的,那么它至少需要'内部',因为方法不能访问其他类的私有方法。提高可视性(使所有内部功能都成为可能)将成为可能,但没有理由让它看起来比需要的更清晰。 – hvd

+0

请注意,生成的闭包类是私有的,因此,从外部看,该方法是*私有*。 – svick

7

从通过C#书杰弗里里希特

编译器自动定义在类

一个新的私有方法的CLR ...编译器会自动为您

创建方法的名称

...由编译器生成的匿名方法总是以私有方式结束 ,该方法是静态的还是非静态的,具体取决于 关于该方法是否访问任何实例成员

因此该方法被声明为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(); 
    } 
} 

编译器优化,并作为@hvd提到的有有将在所有

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

从VS2015起,此信息不再准确。作为优化,新编译器避免了创建静态方法,因为非静态方法在通过委托调用时性能提高很少。 – hvd

+4

@hvd我总是想知道人们是如何知道这样的东西 –

+2

@AlexanderDerck有关开发人员在Roslyn问题跟踪器中提供什么和为什么的详细信息。 – hvd

2

没有拉姆达声明lambda表达式之间的区别使用来自其周围环境的参数(闭包情况)或不使用。见:Why do some C# lambda expressions compile to static methods?

所以这个问题只对非关闭情况有意义,当lambda表达式可以转换成一个委托包装没有任何外部依赖。

您可以将生成的类(基本上包装委托)传递给它,它将始终引用定义程序集中生成的委托。所以如果程序集被引用,你可以从任何地方调用它。

刚刚证实,传递并执行另一个程序集中定义的动作,虽然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); 
     } 
    } 
} 
25

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

我假设“lambda”是指将lambda转换为委托类型。转换为表达式树的Lambdas肯定不是作为方法生成的。

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

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

这个问题有些不一致。假设我告诉过你,lambda是一种公共方法。它没有可从C#访问的名称;你将如何利用其公共性?辅助功能修饰符适用于具有名称的成员。可访问性域的概念给出了域名名称名称分辨率

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

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