24

Tôi đã mở giải pháp của chúng tôi trong Visual Studio 2015 ngày hôm qua và một vài thử nghiệm đơn vị của chúng tôi (chạy tốt trong Visual Studio 2013) bắt đầu thất bại. Digger sâu hơn tôi phát hiện ra nó là bởi vì gọi GetTypes() trên một hội đồng đã được trả lại kết quả khác nhau. Tôi đã có thể tạo ra một trường hợp thử nghiệm rất đơn giản để minh họa nó.Hành vi của Assembly.GetTypes() thay đổi trong Visual Studio 2015

Trong cả Visual Studio 2013 và 2015, tôi đã tạo ứng dụng bảng điều khiển mới bằng .NET Framework 4.5.2. Tôi đặt đoạn mã sau vào cả hai dự án.

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(); 
    } 
} 

Khi tôi chạy trong Visual Studio 2013, tôi nhận được kết quả như sau (như mong đợi).

VS2013Example.Program

Khi tôi chạy trong Visual Studio 2015 tôi nhận được kết quả như sau (không như mong đợi).

VS2015Example.Program

VS2015Example.Program + <> c

Vì vậy, là những gì mà VS2015Example.Program+<>c loại? Hóa ra đó là lambda bên trong phương thức .Where(). Vâng, đúng vậy, bằng cách nào đó mà lambda địa phương đang được tiếp xúc như một loại. Nếu tôi nhận xét số .Where() trong VS2015 thì tôi không còn nhận được dòng thứ hai đó nữa.

Tôi đã sử dụng Beyond Compare để so sánh hai tệp .csproj nhưng khác biệt duy nhất là số phiên bản VS, GUID của dự án, tên của không gian tên và lắp ráp mặc định và VS2015 có tham chiếu đến System.Net .Http mà VS2013 thì không.

Có ai khác nhìn thấy điều này không?

Có ai có giải thích về lý do tại sao biến cục bộ sẽ được hiển thị dưới dạng loại ở cấp hội đồng không?

Trả lời

29

Có ai khác nhìn thấy điều này không?

Có, điều này là do hành vi trình biên dịch mới để nâng biểu thức lambda. Trước đây, nếu một biểu thức lambda không nắm bắt bất kỳ biến cục bộ nào, nó sẽ được lưu trữ như một phương thức tĩnh tại trang gọi, điều này làm cho nhóm biên dịch cần phải nhảy một số vòng để sắp xếp đúng các đối số phương thức và thông số this. Hành vi mới trong Roslyn là tất cả các biểu thức lambda được nâng lên thành một lớp hiển thị, trong đó đại biểu được trưng ra như một phương thức thể hiện trong lớp hiển thị, bỏ qua nếu nó nắm bắt bất kỳ biến cục bộ nào.

Nếu bạn biên soạn lại phương pháp của bạn trong Roslyn, bạn thấy điều này:

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; 
    } 
} 

đâu với trình biên dịch cũ, bạn sẽ thấy điều này:

[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; 
} 

Bạn có thể nhận được kết quả mong muốn bằng cách lọc các lớp học có thuộc tính CompilerGenerated được đính kèm với chúng:

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

Vì nhiều hơn, xem câu hỏi của tôi Delegate caching behavior changes in Roslyn

+1

Cảm ơn thông tin. Dường như một chút đáng sợ vì nó cảm thấy giống như một sự thay đổi có khả năng gây ra rất nhiều mã hiện có đang hoạt động tốt để đột nhiên xuất hiện lỗi. Trong những năm qua tôi đã mất số lần tôi đã viết mã liệt kê các loại trong một hội đồng. Cảm thấy như 'GetTypes()' có thể có một tình trạng quá tải cho phép nhà phát triển nói rõ ràng nếu họ muốn các loại trình biên dịch tạo ra được bao gồm. –

+0

@CraigW. Nên khá dễ dàng để viết một phương pháp mở rộng cho điều đó nhưng tôi hoàn toàn đồng ý đó là một thay đổi phá vỡ tiềm năng bởi vì ngay cả với một phương pháp mở rộng nó sẽ không được gọi theo mặc định, có lẽ bạn nên gửi một vấn đề với đội Roslyn trên github? –

+0

@Craig Đây không phải là một thay đổi đột phá, đó là một chi tiết thực hiện ***. Nếu bạn đã nắm bắt một biến bên trong đại biểu của bạn, bạn sẽ thấy cùng một hành vi. –

Các vấn đề liên quan