2012-05-16 17 views
33

Tôi đã luôn luôn nghĩ rằng nó không thể cho this để được null bên trong cơ thể phương pháp dụ. Sau chương trình đơn giản chứng minh rằng nó là có thể. Đây có phải là một số hành vi được ghi lại không?this == null bên trong .NET instance method - tại sao điều đó lại có thể?

class Foo 
{ 
    public void Bar() 
    { 
     Debug.Assert(this == null); 
    } 
} 

public static void Test() 
{    
    var action = (Action)Delegate.CreateDelegate(typeof (Action), null, typeof(Foo).GetMethod("Bar")); 
    action(); 
} 

CẬP NHẬT

Tôi đồng ý với câu trả lời nói rằng đó là cách phương pháp này được ghi chép lại. Tuy nhiên, tôi không thực sự hiểu hành vi này. Đặc biệt là vì nó không phải là cách C# được thiết kế.

Chúng tôi đã nhận được một báo cáo từ ai đó (có khả năng một trong những nhóm NET sử dụng C# (nghĩ rằng nó vẫn chưa được đặt tên C# tại thời điểm đó)) ai có mã bằng văn bản rằng gọi một phương thức trên một null con trỏ, nhưng họ đã không nhận được một ngoại lệ bởi vì phương pháp đã không truy cập bất kỳ lĩnh vực (tức là "này" là null, nhưng không có gì trong phương pháp được sử dụng nó). Phương pháp đó sau đó được gọi là một phương pháp khác đã sử dụng điểm này và ném một ngoại lệ , và một chút đầu gãi xảy ra sau đó. Sau khi họ tìm thấy nó , họ đã gửi cho chúng tôi một lưu ý về nó. Chúng tôi nghĩ rằng có thể gọi một phương thức trên một cá thể rỗng là một bit lạ . Peter Golde đã làm một số thử nghiệm để xem những gì tác động perf là luôn luôn sử dụng callvirt, và nó là đủ nhỏ mà chúng tôi quyết định để thực hiện thay đổi.

http://blogs.msdn.com/b/ericgu/archive/2008/07/02/why-does-c-always-use-callvirt.aspx

+0

Xem câu trả lời của tôi cho thấy CLR được thiết kế cố ý như thế này đã có trong .NET 1.0.Bài báo bạn trích dẫn là về một tình huống khác (không ủy nhiệm), đó sẽ là một tối ưu hóa trình biên dịch - thay thế một 'callvirt' bằng một cuộc gọi trực tiếp khi loại cá thể có thể được xác định tĩnh. Lưu ý rằng lý do tại sao CLR phải ném 'NullReferenceException' trong' callvirt' là cần tìm kiếm VMT dựa trên tham chiếu 'this'; không chỉ là một mong muốn ngữ nghĩa để kiểm tra các tài liệu tham khảo. –

+0

Related: http://stackoverflow.com/q/3143498/158779 –

+0

IMHO, thật không may là không có cách tiêu chuẩn (có lẽ thông qua các thuộc tính) để xác định rằng một phương thức nên được gọi với 'call' thay vì' callvirt', vì có thể cho phép các loại đóng gói các giá trị [như 'string'] để có các thành viên có thể hoạt động trên các vị trí lưu trữ có giá trị mặc định. Nói 'if (someString.IsNullOrEmpty)' sẽ là IMHO sạch hơn nhiều so với 'if (String.IsNullOrEmpty (someString))'. – supercat

Trả lời

23

Bởi vì bạn đang đi qua null vào firstArgument của Delegate.CreateDelegate

Vì vậy, bạn đang gọi điện thoại một phương pháp dụ về một đối tượng null.

http://msdn.microsoft.com/en-us/library/74x8f551.aspx

Nếu firstArgument là một tham chiếu null và phương pháp là một phương pháp dụ, kết quả phụ thuộc vào chữ ký của các loại loại đại biểu và của phương pháp:

Nếu chữ ký của loại rõ ràng bao gồm tham số ẩn đầu tiên ẩn, đại biểu được cho là đại diện cho phương pháp thể hiện mở .Khi đại biểu được gọi, đối số đầu tiên trong danh sách đối số được chuyển tới tham số phiên bản ẩn của phương thức .

Nếu chữ ký của phương thức và loại đối sánh (nghĩa là, tất cả tham số loại tương thích), thì đại biểu được cho là đóng trên tham chiếu rỗng . Gọi đại biểu giống như gọi một phương thức thể hiện trên một cá thể rỗng, đó không phải là một điều đặc biệt hữu ích đối với .

12

Chắc chắn bạn có thể gọi vào một phương pháp nếu bạn đang sử dụng các hướng dẫn IL cuộc gọi hoặc cách tiếp cận đại biểu. Bạn sẽ đặt bẫy booby này chỉ khi bạn cố gắng truy cập vào các trường thành viên sẽ cung cấp cho bạn NullReferenceException mà bạn đã tìm kiếm.

thử

int x; 
public void Bar() 
{ 
     x = 1; // NullRefException 
     Debug.Assert(this == null); 
} 

Các BCL thậm chí chứa rõ ràng == này kiểm tra null để giúp gỡ rối cho các ngôn ngữ mà không sử dụng callvirt (như C#) tất cả các thời gian. Xem số question này để biết thêm thông tin.

Ví dụ về lớp String có các kiểm tra như vậy. Không có gì bí ẩn về chúng ngoại trừ việc bạn sẽ không thấy sự cần thiết cho chúng bằng các ngôn ngữ như C#.

// Determines whether two strings match. 
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] 
public override bool Equals(Object obj) 
{ 
    //this is necessary to guard against reverse-pinvokes and 
    //other callers who do not use the callvirt instruction 
    if (this == null) 
     throw new NullReferenceException(); 

    String str = obj as String; 
    if (str == null) 
     return false; 

    if (Object.ReferenceEquals(this, obj)) 
     return true; 

    return EqualsHelper(this, str); 
} 
5

Hãy thử tài liệu cho Delegate.CreateDelegate()at msdn.

Bạn đang "thủ công" gọi tất cả mọi thứ, và do đó thay vì chuyển một phiên bản cho con trỏ this, bạn đang bỏ qua null. Vì vậy, nó có thể xảy ra, nhưng bạn phải cố gắng thực sự thực sự khó khăn.

+0

Xem ví dụ sau đây cho một cách thực sự thực sự dễ dàng. http://bradwilson.typepad.com/blog/2008/01/c-30-extension.html –

+0

phương pháp mở rộng có thể "trông" giống như các phương pháp lớp học, nhưng chúng thực sự không. Chúng chỉ là các phương thức tĩnh hoạt động trên một cá thể lớp với một số tính năng bổ sung dễ sử dụng được tích hợp sẵn. Vì vậy, tôi sẽ không gọi đó là điều tương tự. –

+0

Tôi đồng ý với bạn, nhưng danh sách không dừng lại với các phương pháp mở rộng. C++/CLI gọi trực tiếp các phương thức C# không ảo, cung cấp cho bạn chính xác mã nguồn mịn như ví dụ về phương thức mở rộng, lần này dẫn đến một 'giá trị == null' chính hãng ở phía C#. –

5

this là tham chiếu, do đó, không có vấn đề gì với việc là null từ quan điểm của hệ thống kiểu.

Bạn có thể hỏi tại sao NullReferenceException không bị ném. Danh sách đầy đủ các trường hợp khi CLR ném ngoại lệ đó là documented. Trường hợp của bạn không được liệt kê. Có, số điện thoại a callvirt, nhưng đến Delegate.Invoke (see here) thay vì Bar và vì vậy tham chiếu this thực sự là đại diện không null của bạn!

Hành vi bạn thấy có một hệ quả triển khai thú vị cho CLR. Một đại biểu có một tài sản Target (tương ứng với tài liệu tham khảo this) của bạn là khá thường xuyên null, cụ thể là khi đại biểu là tĩnh (tưởng tượng Bar là tĩnh). Bây giờ có, một cách tự nhiên, một lĩnh vực ủng hộ tư nhân cho tài sản, được gọi là _target. Có _target chứa một giá trị rỗng cho một đại biểu tĩnh không? No it doesn't. Nó chứa một tham chiếu đến đại biểu chính nó. Tại sao không null? Bởi vì một null là một mục tiêu hợp pháp của một đại biểu như ví dụ của bạn cho thấy và CLR không có hai hương vị của một con trỏ null để phân biệt các đại biểu tĩnh bằng cách nào đó.

bit này của trivium chứng tỏ rằng với các đại biểu, các mục tiêu rỗng của các phương pháp ví dụ không có suy nghĩ. Bạn vẫn có thể hỏi câu hỏi cuối cùng: nhưng tại sao họ phải được hỗ trợ?

CLR đầu tiên có kế hoạch tham vọng ngay cả đối với các nhà phát triển C++, một mục tiêu được tiếp cận trước với Managed C++ và sau đó với C++/CLI. Một số tính năng ngôn ngữ quá khó khăn đã được bỏ qua, nhưng không có gì thực sự khó khăn về việc hỗ trợ các phương thức thực thi mà không có một cá thể, điều này hoàn toàn bình thường trong C++. Bao gồm hỗ trợ ủy nhiệm.

Câu trả lời cuối cùng là: vì C# và CLR là hai thế giới khác nhau.

More good readingeven better reading để hiển thị thiết kế cho phép các cá thể rỗng hiển thị dấu vết của nó ngay cả trong bối cảnh cú pháp C# rất tự nhiên.

0

đây là tham chiếu chỉ đọc trong các lớp C#. Theo đó và như mong đợi, điều này có thể được sử dụng như bất kỳ tài liệu tham khảo nào khác (ở chế độ chỉ đọc) ...

this == null // readonly - possible 
this = new this() // write - not possible 
Các vấn đề liên quan