2016-03-07 50 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#訪問的名稱;你將如何利用其公共性?輔助功能修飾符適用於具有名稱的成員。可訪問性域的概念給出了域名名稱名稱分辨率

在實踐中,當然編譯器必須爲不可調用的方法的元數據生成一些可訪問性位。在閉包類上生成的方法是內部的,因爲這是使它們可以被驗證的最方便的方法。不使用閉包生成的方法可以是私有的。

再一次,這些都不是必需的,所有這些都是實施細節可能會發生變化。您不應該試圖利用編譯器的代碼生成細節。