2012-01-08 38 views
8

Một thời gian ngắn tôi biên soạn hai phiên bản mã, một phiên bản sử dụng (Nullable<T>)x.GetValueOrDefault(y) và một sử dụng (Nullable<T>)x ?? y).Null coalesce operator affications?

Sau khi biên dịch sang IL, tôi nhận thấy rằng toán tử kết hợp rỗng được chuyển thành cuộc gọi GetValueOrDefault.

Vì đây là phương thức mà một biểu thức có thể được truyền qua được đánh giá trước khi thực hiện phương thức, y dường như luôn được thực thi.

Ví dụ:

using System; 

public static class TestClass 
{ 
    private class SomeDisposable : IDisposable 
    { 
     public SomeDisposable() 
     { 
      // Allocate some native resources 
     } 

     private void finalize() 
     { 
      // Free those resources 
     } 

     ~SomeDisposable() 
     { 
      finalize(); 
     } 

     public void Dispose() 
     { 
      finalize(); 
      GC.SuppressFinalize(this); 
     } 
    } 

    private struct TestStruct 
    { 
     public readonly SomeDisposable _someDisposable; 
     private readonly int _weirdNumber; 

     public TestStruct(int weirdNumber) 
     { 
      _weirdNumber = weirdNumber; 
      _someDisposable = new SomeDisposable(); 
     } 
    } 

    public static void Main() 
    { 
     TestStruct? local = new TestStruct(0); 

     TestStruct local2 = local ?? new TestStruct(1); 

     local2._someDisposable.Dispose(); 
    } 
} 

Dường như kết quả vào một đối tượng làm mích, và có lẽ tác động hiệu suất quá.

Trước hết, điều này có đúng không? Hoặc JIT hoặc một cái gì đó giống như thay đổi mã ASM thực sự thực hiện?

Và thứ hai ai đó có thể giải thích lý do tại sao ứng dụng này có hành vi này?

LƯU Ý: Đây chỉ là một ví dụ, nó không dựa trên mã thực, và vui lòng không đưa ra các nhận xét như 'đây là mã xấu'.

IL DASM:
Được rồi, khi tôi biên soạn này với .Net Framework 2.0 nó dẫn trong mã giống hệt với gọi liên hiệp vô GetValueOrDefault. Với .Net Framework 4.0, nó tạo ra hai mã này:

GetValueOrDefault:

.method private hidebysig static void Main() cil managed 
{ 
    .entrypoint 
    // Code size  19 (0x13) 
    .maxstack 2 
    .locals init ([0] valuetype [mscorlib]System.Nullable`1<int32> nullableInt, 
      [1] int32 nonNullableInt) 
    IL_0000: nop 
    IL_0001: ldloca.s nullableInt 
    IL_0003: initobj valuetype [mscorlib]System.Nullable`1<int32> 
    IL_0009: ldloca.s nullableInt 
    IL_000b: ldc.i4.1 
    IL_000c: call  instance !0 valuetype [mscorlib]System.Nullable`1<int32>::GetValueOrDefault(!0) 
    IL_0011: stloc.1 
    IL_0012: ret 
} // end of method Program::Main 

Null liên hiệp:

.method private hidebysig static void Main() cil managed 
{ 
    .entrypoint 
    // Code size  32 (0x20) 
    .maxstack 2 
    .locals init (valuetype [mscorlib]System.Nullable`1<int32> V_0, 
      int32 V_1, 
      valuetype [mscorlib]System.Nullable`1<int32> V_2) 
    IL_0000: nop 
    IL_0001: ldloca.s V_0 
    IL_0003: initobj valuetype [mscorlib]System.Nullable`1<int32> 
    IL_0009: ldloc.0 
    IL_000a: stloc.2 
    IL_000b: ldloca.s V_2 
    IL_000d: call  instance bool valuetype [mscorlib]System.Nullable`1<int32>::get_HasValue() 
    IL_0012: brtrue.s IL_0017 
    IL_0014: ldc.i4.1 
    IL_0015: br.s  IL_001e 
    IL_0017: ldloca.s V_2 
    IL_0019: call  instance !0 valuetype [mscorlib]System.Nullable`1<int32>::GetValueOrDefault() 
    IL_001e: stloc.1 
    IL_001f: ret 
} // end of method Program::Main 

Khi nó quay ra rằng đây không còn là trường hợp và bỏ qua cuộc gọi GetValueOrDefault hoàn toàn khi HasValue trả về sai.

+0

Nếu 'y' là một cuộc gọi đến một phương thức dẫn đến một đối tượng cần được xử lý thì bạn sẽ bị rò rỉ trong bất kỳ trường hợp nào' x' là rỗng. –

+0

@ M.Babcock Không thực sự là một sự rò rỉ, chỉ là một sự trì hoãn làm sạch bộ nhớ.Và nhìn vào ví dụ đã sửa đổi, tôi hy vọng điều này sẽ giải thích vấn đề tốt hơn. – Aidiakapi

+0

Thực ra tôi đã nói sai, nếu 'local' không phải là null và phương thức này thực sự được gọi thì bạn sẽ bị rò rỉ vì kết quả sẽ được thu thập từ GC mà không bị xử lý. Để trả lời câu hỏi liệu phương thức này có thực sự được gọi mỗi lần không, bạn có thể đặt một điểm ngắt trong phương thức và chạy nó. –

Trả lời

6

Sau khi biên dịch sang IL, tôi nhận thấy rằng toán tử kết hợp rỗng được chuyển thành lệnh gọi GetValueOrDefault.

x ?? y được chuyển thành x.HasValue ? x.GetValueOrDefault() : y. Nó không được chuyển thành x.GetValueOrDefault(y), và nó sẽ là một lỗi trình biên dịch nếu nó được. Bạn đúng, không nên đánh giá y nếu x không phải là rỗng và không.

Sửa: Nếu việc thẩm định y thể được chứng minh miễn phí các tác dụng phụ (nơi "tác dụng phụ" bao gồm "ném một ngoại lệ"), sau đó chuyển dạng cho x.GetValueOrDefault(y) sẽ không nhất thiết thể sai, nhưng nó vẫn là một chuyển đổi mà tôi không nghĩ rằng trình biên dịch thực hiện: không có tất cả nhiều tình huống mà tối ưu hóa đó sẽ hữu ích.

+0

Tôi đã thực hiện thử nghiệm này một thời gian dài trước đây bằng cách sử dụng trình biên dịch .Net Framework 2.0, tôi cũng đã thực hiện thêm một số tài nguyên, và từ nguồn cung cấp mã tham chiếu của MS rõ ràng là 'GetValueOrDefault' trả về giá trị nội bộ trực tiếp, trong khi' get_Value' thực hiện kiểm tra đầu tiên, đó là nhiều khả năng tại sao nó chọn cho 'GetValueOrDefault' thay vì' get_Value'. Cảm ơn bạn :) – Aidiakapi

+0

Vâng, thuộc tính 'Value' getter về cơ bản là' if (! HasValue) throw; trả về GetValueOrDefault(); '- do đó, có ít điểm trong việc gọi nó nếu bạn đã xác định xem đối tượng nullable có một giá trị hay không :) – hvd

+0

Hoặc chính xác' if (! HasValue) throw' ... '; giá trị trả về; Trong khi 'GetValueOrDefault()' là 'giá trị trả về;'. Khá thú vị vì nó có giá trị vì tính bất biến của cấu trúc (hầu hết), nó đơn giản phải tạo lại 'Nullable ' wrapper xung quanh 'T' vì vậy không có nhiều sử dụng để kiểm tra HasValue trong' GetValueOrDefault() 'vì cấu trúc tự động bắt đầu ra mặc định cho tất cả các bit 0. – Aidiakapi

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