2015-12-30 13 views
11

Với hai phương pháp:gọi thay vì gọi trong trường hợp C# 6 mới? " rỗng kiểm tra

static void M1(Person p) 
    { 
     if (p != null) 
     { 
      var p1 = p.Name; 
     } 
    } 

    static void M2(Person p) 
    { 
     var p1 = p?.Name; 
    } 

Tại sao mã M1 IL sử dụng callvirt:

IL_0007: brfalse.s IL_0012 
IL_0009: nop 
IL_000a: ldarg.0 
IL_000b: callvirt instance string ConsoleApplication4.Person::get_Name() 

và M2 IL sử dụng call:

brtrue.s IL_0007 
IL_0004: ldnull 
IL_0005: br.s  IL_000d 
IL_0007: ldarg.0 
IL_0008: call  instance string ConsoleApplication4.Person::get_Name() 

Tôi chỉ có thể đoán rằng bởi vì trong M2 chúng tôi biết rằng p không phải là null và giống như

của nó
new MyClass().MyMethod(); 

Có đúng không?

Nếu có, điều gì xảy ra nếu p sẽ không có trong chuỗi khác?

+0

Thật tuyệt khi cho biết cách một thành viên ảo thực sự được gọi khi được ghi đè. Cụ thể trong sự hiện diện của '? .'. Hành vi khi một thành viên ghi đè là 'niêm phong' và được triệu gọi cụ thể là gì? Nếu nếu nó vẫn là 'callvirt', nó có thể là một cơ hội tối ưu hóa trong Roslyn :) – leppie

+0

@leppie C# tạo' callvirt' cho việc này. Nhưng tôi không biết điều gì xảy ra trong thời gian chạy. –

Trả lời

5

Tôi nghĩ đó là rõ ràng bây giờ,

Đây là một cách dễ dàng và thread-safe để kiểm tra null trước khi bạn kích hoạt một sự kiện. Lý do an toàn chủ đề là tính năng chỉ đánh giá phía bên trái một lần và giữ nó ở một biến tạm thời. MSDN

Vì vậy, an toàn khi sử dụng call hướng dẫn tại đây.

Tôi đã viết một blog post về sự khác biệt giữa callcallvirt và tại sao C# tạo callvirt

Cảm ơn Dan Lyons cho liên kết MSDN.

7

callvirt trong M1 là standard C# code generation. Nó cung cấp ngôn ngữ đảm bảo rằng một thể hiện phương thức không bao giờ có thể được gọi với một tham chiếu null. Nói cách khác, nó đảm bảo rằng p != null và tạo ra NullReferenceException nếu nó là null. Kiểm tra rõ ràng của bạn không thay đổi điều đó.

Đảm bảo này là khá tốt đẹp, gỡ lỗi NRE được khá lông nếu nó là this đó là null. Dễ dàng hơn nhiều để chẩn đoán các rủi ro tại các cuộc gọi trang web thay vào đó, trình gỡ lỗi có thể nhanh chóng cho bạn thấy rằng nó là p đó là kẻ gây rối.

Nhưng tất nhiên callvirt không miễn phí, mặc dù chi phí rất thấp, một hướng dẫn xử lý bổ sung khi chạy. Vì vậy, nếu nó có thể được thay thế bằng call thì mã sẽ nhanh hơn một nửa nano giây, cho hay lấy. Nó trong thực tế có thể với các nhà điều hành elvis vì nó đã đảm bảo rằng tham chiếu không phải là null vì vậy trình biên dịch C# 6 đã lợi dụng điều đó và tạo ra cuộc gọi thay vì callvirt.

+0

cảm ơn các chi tiết. Tôi đã viết về nó một bài viết trong liên kết ở trên nhưng tôi không biết về một nửa nano giây :) –

1

Bắt đầu với thực tế là callvirt được sử dụng thay vì call vì quy tắc C# mà các đối tượng rỗng có thể không có phương thức được gọi trên chúng, ngay cả khi .NET cho phép. Bây giờ, trong cả hai phương pháp của bạn, chúng tôi có thể hiển thị tĩnh rằng p không phải là rỗng và như vậy sử dụng call thay vì callvirt sẽ không vi phạm quy tắc C# này và như vậy là tối ưu hóa hợp lý.

Trong khi if (a != null) a.b vv là thành ngữ phổ biến, cần phân tích để nhận ra rằng a không thể rỗng tại thời điểm b được sử dụng. Thêm phân tích đó vào trình biên dịch sẽ thực hiện công việc chỉ định, triển khai, thử nghiệm và liên tục thử nghiệm đối với các lỗi hồi quy được giới thiệu bởi các thay đổi khác.

a?.b vượt quá thành ngữ, ở chỗ nó sử dụng toán tử ?. mà C# phải "biết". Vì vậy, C# phải có mã để biến điều này thành một kiểm tra null, tiếp theo là truy cập thành viên. Vì vậy, trình biên dịch phải biết rằng tại thời điểm truy cập thành viên diễn ra, a không phải là rỗng. Như vậy logic để "biết" việc sử dụng call là an toàn đã được thực hiện. Nó không có công việc phân tích thêm để nhận ra rằng call có thể được sử dụng.

Vì vậy, trường hợp đầu tiên sẽ yêu cầu một loạt công việc phụ để sử dụng call và có khả năng giới thiệu lỗi, trong khi trường hợp thứ hai phải thực hiện công việc đó, vì vậy nó có thể là tốt.

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