2012-07-10 12 views
16

Khi từ khóa delegate được sử dụng trong C#, trình biên dịch C# sẽ tự động tạo ra một lớp bắt nguồn từ lớp System.MulticastDelegate.Trình biên dịch tạo lớp kín cho từ khóa đại biểu chứa các phương thức ảo

Lớp trình biên dịch được tạo này chứa 3 phương pháp: Invoke, BeginInvoke and EndInvoke.

Tất cả ba phương pháp này được đánh dấu public virtual extern nhưng thú vị là chính lớp đó được đánh dấu sealed.

Các phương pháp ảo được xác định trong một lớp được niêm phong không chỉ là các cảnh cáo như phản trực giác mà thực sự là bất hợp pháp trong C#.

Vì vậy, câu hỏi của tôi là, có một lý do cụ thể cho điều này hay là nó chỉ là một trong những điều vô hại được thực hiện trong tâm trí một số tăng cường tương lai giả định?

Sửa 1:

lý do có thể là để buộc sử dụng IL opcode 'callvirt' như trái ngược với 'gọi' để đối tượng delegate luôn được chọn for null bởi CLR trước khi cố gắng để thực hiện một trong ba phương pháp? Mặc dù tôi không thấy lý do tại sao một delegate phải là một trường hợp đặc biệt về mặt này.

Cũng không phải là nó một hit hiệu suất để buộc sử dụng callvirt (mặc dù nó có thể là rất nhỏ)

Chỉnh sửa 2:

Added CIL thẻ, khi nó quay ra rằng C# cách xác định các đại biểu thực tế được yêu cầu bởi tiêu chuẩn CIL. Trạng thái chuẩn (sau đây không phải là toàn văn)

Đại biểu phải có loại cơ sở là System.Delegate. Các đại biểu phải được tuyên bố bịt kín và các thành viên duy nhất mà một đại biểu sẽ có là hoặc hai hoặc tất cả bốn phương pháp đầu tiên được chỉ định tại đây. Các phương thức này sẽ được khai báo thời gian chạy và được quản lý. Họ sẽ không có một cơ thể , vì cơ thể đó sẽ được tạo ra tự động bởi VES. Các phương thức khác có sẵn trên các đại biểu được kế thừa từ lớp System.Delegate trong Thư viện lớp cơ sở. Các phương pháp đại biểu là:

  1. Các constructor dụ
  2. các phương thức Invoke sẽ ảo
  3. Phương pháp BeginInvoke, nếu có, sẽ là ảo
  4. Phương pháp EndInvoke sẽ ảo

Vì vậy, điều này chắc chắn không phải là một tác dụng phụ của quá trình biên dịch hoặc tương tự như các đầu ra trình biên dịch thú vị khác.

Nếu tiêu chuẩn nhấn mạnh điều gì đó, nó phải vì một số lý do chính đáng và lý do hợp lý.

Vì vậy, câu hỏi đặt ra là tại sao tiêu chuẩn CIL cho các đại biểu nhấn mạnh vào niêm phong và ảo cùng một lúc?

Liệu bắt nằm đây ?:

Họ sẽ không có một cơ thể, kể từ khi cơ thể được tạo ra tự động bởi VES.

Chúng được đánh dấu ảo sao cho nội dung được tạo VES/CLR có thể được thực hiện khi gọi các phương pháp này?

+8

Đừng quên, những gì không hợp lệ trong C# có thể hoàn toàn hợp lệ trong IL - có những trường hợp khác hiện tại, đây có thể là trường hợp khác. Nhưng +1 cho tìm kiếm thú vị. –

+0

Tôi đã thử rằng với ILDasm và từ khóa ghi đè được dịch sang ảo trong IL. – MBen

+0

@MBen có nhưng đối với lớp đại biểu được tạo ra C# thì không có phương thức lớp cơ sở tương ứng nào để ghi đè. Do đó từ khóa ảo ở đây không phải là kết quả của 'override' :) –

Trả lời

3

Như tôi đã lưu ý trong câu hỏi của tôi rằng sự bất thường ảo được niêm phong này thực tế được bắt buộc bởi tiêu chuẩn CIL. Vẫn chưa rõ lý do tại sao tiêu chuẩn CIL đề cập cụ thể rằng các phương thức ủy nhiệm Invoke, BeginInvokeEndInvoke phải là ảo trong khi đồng thời yêu cầu đóng dấu Delegate lớp kế thừa.

Ngoài ra, sau khi trải qua mã SSCLI tôi đã học được rằng tối ưu hóa nội bộ của trình biên dịch JIT tự động dịch bất kỳ cuộc gọi callvirt nào trên một phương thức ảo của một lớp niêm phong thành cuộc gọi bình thường. Điều này có nghĩa là các đại biểu không bị ảnh hưởng khi nhấn Invoke (hoặc bất kỳ phương thức nào khác) được gọi thông qua lệnh callvirt mặc dù được đánh dấu là ảo trong IL.

Khi cuộc gọi của đại biểu được gọi, CLR tự động phát ra một cơ thể được tối ưu hóa cao cho phương pháp này trái ngược với việc biên dịch mã IL để tạo nội dung cho phương thức 'bình thường'. Điều này không liên quan gì đến việc được đánh dấu virtual trong IL.

Tôi cũng đã xác minh bằng tay sửa đổi mã IL và lắp ráp lại nó mà ảo có thể được xóa an toàn khỏi mã IL của lớp đại biểu đã tạo. Việc lắp ráp được tạo ra mặc dù bị vi phạm tiêu chuẩn CIL chạy hoàn toàn tốt đẹp.

.class private auto ansi beforefieldinit MainApp 
     extends [mscorlib]System.Object 
{ 
    .class auto ansi sealed nested private Echo 
     extends [mscorlib]System.MulticastDelegate 
    { 
    .method public hidebysig specialname rtspecialname 
      instance void .ctor(object 'object', 
           native int 'method') runtime managed 
    { 
    } // end of method Echo::.ctor 

    .method public hidebysig instance int32 Invoke(int32 i) runtime managed 
    { 
    } // end of method Echo::Invoke 

    .method public hidebysig instance class [mscorlib]System.IAsyncResult 
      BeginInvoke(int32 i, 
         class [mscorlib]System.AsyncCallback callback, 
         object 'object') runtime managed 
    { 
    } // end of method Echo::BeginInvoke 

    .method public hidebysig instance int32 EndInvoke(class [mscorlib]System.IAsyncResult result) runtime managed 
    { 
    } // end of method Echo::EndInvoke 

    } // end of class Echo 

    .method public hidebysig static void Main() cil managed 
    { 
    .entrypoint 
    // Code size  34 (0x22) 
    .maxstack 3 
    .locals init ([0] class MainApp app, 
      [1] class MainApp/Echo dele) 
    IL_0000: nop 
    IL_0001: newobj  instance void MainApp::.ctor() 
    IL_0006: stloc.0 
    IL_0007: ldloc.0 
    IL_0008: ldftn  instance int32 MainApp::DoEcho(int32) 
    IL_000e: newobj  instance void MainApp/Echo::.ctor(object, 
                  native int) 
    IL_0013: stloc.1 
    IL_0014: ldloc.1 
    IL_0015: ldc.i4.5 
    //callvirt can also be replaced by call without affecting functionality 
    // since delegate object is essentially not null here 
    IL_0016: callvirt instance int32 MainApp/Echo::Invoke(int32) 
    IL_001b: call  void [mscorlib]System.Console::WriteLine(int32) 
    IL_0020: nop 
    IL_0021: ret 
    } // end of method MainApp::Main 

    .method private hidebysig instance int32 
      DoEcho(int32 i) cil managed 
    { 
    // Code size  7 (0x7) 
    .maxstack 1 
    .locals init ([0] int32 CS$1$0000) 
    IL_0000: nop 
    IL_0001: ldarg.1 
    IL_0002: stloc.0 
    IL_0003: br.s  IL_0005 

    IL_0005: ldloc.0 
    IL_0006: ret 
    } // end of method MainApp::DoEcho 

    .method public hidebysig specialname rtspecialname 
      instance void .ctor() cil managed 
    { 
    // Code size  7 (0x7) 
    .maxstack 8 
    IL_0000: ldarg.0 
    IL_0001: call  instance void [mscorlib]System.Object::.ctor() 
    IL_0006: ret 
    } // end of method MainApp::.ctor 

} // end of class MainApp 

Lưu ý rằng tôi đã chuyển đổi các phương pháp ảo thành các phương pháp thể hiện thông thường.

Vì điều này thay đổi IL chạy hoàn toàn tốt nên nó chứng minh rằng các phương thức ảo bắt buộc chuẩn trong lớp đại biểu được niêm phong là không cần thiết. Chúng có thể là các phương thức thể hiện bình thường. Vì vậy, trong mọi khả năng bất thường này là nhấn mạnh rằng gọi ba phương thức đại biểu này sẽ dẫn đến việc gọi một số phương thức khác (tức là đa hình thời gian chạy giống như các phương pháp ảo 'bình thường') hoặc điều này đã xảy ra như vậy để đáp ứng một số nâng cao giả thuyết trong tương lai liên quan đến các đại biểu.

+1

Bạn có thể tham khảo tôi đến nơi bạn đã học về 'cơ thể được tối ưu hóa cao đối lập để biên dịch IL' không? –

3

Đây là một tác dụng phụ của quá trình biên dịch. Tôi không biết lý do chính xác cho điều này, có nhiều ví dụ về loại hành vi này. Ví dụ, một lớp tĩnh đã biên dịch trở thành một lớp được niêm phong trừu tượng (vì vậy bạn không thể tạo một thể hiện của nó, và bạn không thể kế thừa từ nó).

+0

Điểm rất thú vị về các lớp tĩnh:) ... nhưng tôi nghĩ rằng trừu tượng kín là khá nhiều cách duy nhất ngữ nghĩa của lớp tĩnh có thể được đại diện trong MSIL như 'bạn có thể không tạo ra một thể hiện của nó, bạn không thể kế thừa từ nó 'cùng với' chính lớp đó không chứa bất kỳ thành viên cá thể nào 'là hợp đồng cho tĩnh. –

+0

Vui lòng xem edit2 của tôi cho câu hỏi. –

1

Có vẻ như không dành riêng cho đại biểu. tôi đã cố gắng ví dụ này:

public abstract class Base 
    { 
     public abstract void Test(); 
    } 

    public sealed class Derived : Base 
    { 
     public override void Test() 
     { 
      throw new NotImplementedException(); 
     } 
    } 

và ildasm tôi có được điều này để thực hiện các thử nghiệm():

.method public hidebysig virtual instance void 
     Test() cil managed 
{ 
    // Code size  7 (0x7) 
    .maxstack 8 
    IL_0000: nop 
    IL_0001: newobj  instance void [mscorlib]System.NotImplementedException::.ctor() 
    IL_0006: throw 
} // end of method Derived::Test 

Có thể rằng từ khóa override không phải là một từ khóa CLR.

+1

Cảm ơn bạn đã trả lời. Đây là một tìm kiếm rất thú vị khác nhưng sự khác biệt với delegate là ba phương thức mà tôi đề cập đến là các phương thức mới được tạo ra và không ghi đè. Ghi đè về cơ bản là ảo và như bạn đã đề cập, tôi cũng nghĩ rằng MSIL không có từ khóa ghi đè tương ứng. –

+0

Yep điểm tốt. – MBen

+0

Chỉ là một điểm tôi vừa học được. Sự vắng mặt của từ khóa 'newslot' trước khi ảo nghĩa rằng phương pháp ảo là một ghi đè ... –

4

Bạn đang bị vấp bởi bộ tách rời mà bạn đã sử dụng để xem định nghĩa loại. Mà phải dịch IL trở lại một ngôn ngữ dễ nhận biết, như C#. Điều này nói chung không thể làm được với sự trung thành đầy đủ, các quy tắc cho IL là không phải là giống như các quy tắc ngôn ngữ C#. Điều này không chỉ xảy ra đối với các đại biểu, một phương thức triển khai giao diện cũng là ảo, mặc dù bạn không khai báo nó ảo trong mã C# của bạn.

Để tiếp tục lầy lội trên mặt nước, IL thực sự cho phép trình biên dịch phát ra cuộc gọi không ảo cho một phương pháp ảo nếu nó có thể xác định đối tượng mục tiêu từ phân tích mã. Nhưng điều đó sẽ không bao giờ xảy ra đối với một cuộc gọi đại biểu hoặc giao diện. Và IL cho phép thực hiện một cuộc gọi ảo đến một phương thức không phải ảo, một trình biên dịch C# thực hiện với gusto để thực hiện bảo đảm rằng một phương thức thể hiện không bao giờ được gọi với một số rỗng.

Nhưng việc sử dụng C# đó là một mẹo thông minh, chỉ được phát hiện sau khi CLR được thiết kế. Mục đích ban đầu của virtual chắc chắn là chú thích rằng phương thức nên được gọi với Callvirt. Cuối cùng nó không quan trọng bởi vì trình biên dịch là nhận thức của đại biểu và hành vi giao diện và sẽ luôn luôn phát ra Callvirt. Và cuộc gọi phương thức thực tế được thực hiện trong mã CLR, giả sử kích hoạt Callvirt.

+0

Cảm ơn bạn đã giải thích. Vui lòng xem edit2 của tôi cho câu hỏi. –