24

我昨天在Visual Studio 2015中打開了我們的解決方案,我們的一些單元測試(在Visual Studio 2013中運行正常)開始失敗。深挖掘機我發現這是因爲在程序集上調用GetTypes()返回不同的結果。我已經能夠創建一個非常簡單的測試案例來說明它。Assembly.GetTypes()在Visual Studio 2015中的行爲已更改

在Visual Studio 2013和2015中,我使用.NET Framework 4.5.2創建了一個新的控制檯應用程序。我把這兩個項目中的代碼。

class Program 
{ 
    static void Main(string[] args) 
    { 
     var types = typeof(Program).Assembly.GetTypes() 
       .Where(t => !t.IsAbstract && t.IsClass); 

     foreach (var type in types) 
     { 
      Console.WriteLine(type.FullName); 
     } 

     Console.ReadKey(); 
    } 
} 

當我在Visual Studio 2013中運行時,我得到以下輸出(如預期的那樣)。

VS2013Example.Program

當我運行在Visual Studio 2015年我得到下面的輸出(未如預期)。

VS2015Example.Program

VS2015Example.Program + <>ç

那麼,什麼是VS2015Example.Program+<>c類型?原來,這是.Where()方法中的lambda。是的,這是正確的,不知何故,本地lambda被暴露爲類型。如果我在VS2015中註釋掉.Where(),那麼我不會再獲得第二條線。

我已經使用Beyond Compare來比較兩個.csproj文件,但唯一的區別是VS版本號,項目GUID,默認命名空間和程序集的名稱,以及VS2015有一個對System.Net的引用.Http VS2013沒有。

有沒有其他人看到過這個?

有沒有人有解釋爲什麼一個局部變量會在彙編級別作爲類型暴露?

回答

29

有沒有其他人看到過這個?

是的,這是由提升lambda表達式的新編譯器行爲引起的。

以前,如果一個lambda表達式沒有捕獲任何局部變量,它將作爲靜態方法緩存在調用站點,這使編譯器團隊需要跳過一些環節,以便正確地對齊方法參數和this參數。 Roslyn中的新行爲是,所有的lambda表達式都被提升到一個顯示類中,其中代表作爲顯示類中的一個實例方法公開,不管它是否捕獲任何局部變量。

如果您在羅斯林反編譯你的方法,你會看到這一點:

private static void Main(string[] args) 
{ 
    IEnumerable<Type> arg_33_0 = typeof(Program).Assembly.GetTypes(); 
    Func<Type, bool> arg_33_1; 
    if (arg_33_1 = Program.<>c.<>9__0_0 == null) 
    { 
     arg_33_1 = Program.<>c.<>9__0_0 = 
         new Func<Type, bool>(Program.<>c.<>9.<Main>b__0_0); 
    } 
    using (IEnumerator<Type> enumerator = arg_33_0.Where(arg_33_1).GetEnumerator()) 
    { 
     while (enumerator.MoveNext()) 
     { 
      Console.WriteLine(enumerator.Current.FullName); 
     } 
    } 
    Console.ReadKey(); 
} 

[CompilerGenerated] 
[Serializable] 
private sealed class <>c 
{ 
    public static readonly Program.<>c <>9; 
    public static Func<Type, bool> <>9__0_0; 
    static <>c() 
    { 
     // Note: this type is marked as 'beforefieldinit'. 
     Program.<>c.<>9 = new Program.<>c(); 
    } 
    internal bool <Main>b__0_0(Type t) 
    { 
     return !t.IsAbstract && t.IsClass; 
    } 
} 

凡與老編譯器,你會看到這一點:

[CompilerGenerated] 
private static Func<Type, bool> CS$<>9__CachedAnonymousMethodDelegate1; 

private static void Main(string[] args) 
{ 
    IEnumerable<Type> arg_34_0 = typeof(Program).Assembly.GetTypes(); 
    if (Program.CS$<>9__CachedAnonymousMethodDelegate1 == null) 
    { 
     Program.CS$<>9__CachedAnonymousMethodDelegate1 = 
          new Func<Type, bool>(Program.<Main>b__0); 
    } 
    IEnumerable<Type> types = 
       arg_34_0.Where(Program.CS$<>9__CachedAnonymousMethodDelegate1); 

    foreach (Type type in types) 
    { 
     Console.WriteLine(type.FullName); 
    } 
    Console.ReadKey(); 
} 

[CompilerGenerated] 
private static bool <Main>b__0(Type t) 
{ 
    return !t.IsAbstract && t.IsClass; 
} 

您可以通過過濾得到期望的結果有CompilerGenerated屬性附加到他們:

var types = typeof(Program) 
      .Assembly 
      .GetTypes() 
      .Where(t => !t.IsAbstract && 
         t.IsClass && 
         Attribute.GetCustomAttribute(
          t, typeof (CompilerGeneratedAttribute)) == null); 

For更多,看我的問題Delegate caching behavior changes in Roslyn

+1

感謝您的信息。看起來有點可怕,因爲它感覺像一個變化,可能會導致很多現有的代碼工作得很好,突然出現錯誤。多年來,我已經忘記了我編寫代碼的次數,這些代碼列舉了程序集中的類型。感覺像'GetTypes()'應該可能有一個重載,讓開發人員明確地聲明他們是否希望包含編譯器生成的類型。 –

+0

@CraigW。應該很容易編寫一個擴展方法,但我完全同意這是一個潛在的重大變化,因爲即使使用擴展方法,它不會默認調用,也許你應該在github上提交Roslyn團隊的問題? –

+0

@克雷格這不是一個突破性的變化,那是一個***實現細節***。如果你在委託中捕獲了一個變量,你會看到相同的行爲。 –