2013-10-14 20 views
15

tôi nhận được một bất ngờ NullReferenceException khi tôi chạy mã này, bỏ qua các tham số fileSystemHelper (và do đó mặc định nó để null):Tại sao toán tử không hợp nhất (??) hoạt động trong tình huống này?

public class GitLog 
    { 
    FileSystemHelper fileSystem; 

    /// <summary> 
    /// Initializes a new instance of the <see cref="GitLog" /> class. 
    /// </summary> 
    /// <param name="pathToWorkingCopy">The path to a Git working copy.</param> 
    /// <param name="fileSystemHelper">A helper class that provides file system services (optional).</param> 
    /// <exception cref="ArgumentException">Thrown if the path is invalid.</exception> 
    /// <exception cref="InvalidOperationException">Thrown if there is no Git repository at the specified path.</exception> 
    public GitLog(string pathToWorkingCopy, FileSystemHelper fileSystemHelper = null) 
     { 
     this.fileSystem = fileSystemHelper ?? new FileSystemHelper(); 
     string fullPath = fileSystem.GetFullPath(pathToWorkingCopy); // ArgumentException if path invalid. 
     if (!fileSystem.DirectoryExists(fullPath)) 
      throw new ArgumentException("The specified working copy directory does not exist."); 
     GitWorkingCopyPath = pathToWorkingCopy; 
     string git = fileSystem.PathCombine(fullPath, ".git"); 
     if (!fileSystem.DirectoryExists(git)) 
      { 
      throw new InvalidOperationException(
       "There does not appear to be a Git repository at the specified location."); 
      } 
     } 

Khi tôi duy nhất bước mã trong trình gỡ lỗi, sau khi tôi bước qua dòng đầu tiên (với các nhà điều hành ??) sau đó fileSystem vẫn có giá trị null, như trong ảnh chụp màn hình này (bước trên dòng tiếp theo ném NullReferenceException): When is null not null?

Đây không phải là những gì tôi mong đợi! Tôi hy vọng toán tử kết hợp null để phát hiện rằng tham số là null và tạo ra một new FileSystemHelper(). Tôi đã nhìn vào mã này cho các lứa tuổi và không thể nhìn thấy những gì là sai với nó.

ReSharper chỉ ra rằng trường này chỉ được sử dụng trong phương pháp này, vì vậy có khả năng có thể được chuyển đổi thành một biến địa phương ... vì vậy tôi đã thử và đoán những gì? Nó đã làm việc. Vì vậy, tôi đã sửa chữa của tôi, nhưng tôi không thể cho cuộc sống của tôi thấy lý do tại sao mã trên không hoạt động. Tôi cảm thấy như tôi đang ở trên các cạnh của việc học một cái gì đó thú vị về C#, hoặc là tôi đã làm một cái gì đó thực sự câm. Có ai có thể thấy những gì đang xảy ra ở đây không?

+0

Bạn đã nói rằng 'fileSystemHelper' là' null' trong các tham số phương thức, tôi không chắc chắn, nhưng nó có thể có liên quan gì đó với nó. Nhưng sau đó một lần nữa, tôi đoán. – Tico

+2

Bạn có chắc chắn NRE không xảy ra từ * trong * GetFullPath (bỏ qua những gì đồng hồ hiển thị) không? Tôi thấy không có gì với mã ở trên mà sẽ dẫn đến hành vi nói. – user2864740

+0

OK, khi thoát khỏi VisualStudio để làm điều gì đó khác sau đó tải lại nó, mọi thứ đang hoạt động ngay bây giờ và tôi không thể tái tạo vấn đề. Tôi _think_ điều này có thể là một vấn đề bộ nhớ đệm kỳ lạ với Á hậu thử nghiệm đơn vị ReSharper. Tôi đang sử dụng một bài kiểm tra MSpec đơn giản để thực hiện mã. ReSharper mất một bản sao bóng của hội đồng khi chạy thử nghiệm đơn vị và đôi khi, chỉ đôi khi, bản sao bóng có vẻ như bị 'kẹt', tôi đã nhìn thấy nó xảy ra một vài lần trước đây. Rất có thể, tôi đã thực sự chạy mã cũ, mặc dù tôi đã xây dựng lại mọi thứ theo cách thủ công. Đó là lời giải thích tốt nhất mà tôi có ... –

Trả lời

12

tôi đã sao chép nó trong VS2012 với đoạn mã sau:

public void Test() 
{ 
    TestFoo(); 
} 

private Foo _foo; 

private void TestFoo(Foo foo = null) 
{ 
    _foo = foo ?? new Foo(); 
} 

public class Foo 
{ 
} 

Nếu bạn đặt một breakpoint ở phần cuối của phương pháp TestFoo, bạn sẽ mong đợi để xem bộ biến _foo, nhưng nó vẫn sẽ hiển thị như null trong trình gỡ rối.

Nhưng nếu sau đó bạn thực hiện bất cứ điều gì với _foo, sau đó xuất hiện chính xác. Ngay cả một nhiệm vụ đơn giản như

_foo = foo ?? new Foo(); 
var f = _foo; 

Nếu bạn bước qua nó, bạn sẽ thấy rằng _foo lãm rỗng cho đến khi nó được gán cho f.

Điều này nhắc tôi về hành vi thực thi bị trì hoãn, chẳng hạn như với LINQ, nhưng tôi không thể tìm thấy bất kỳ điều gì có thể xác nhận điều đó.

Hoàn toàn có thể là đây chỉ là một dấu nháy đơn của trình gỡ lỗi. Có lẽ ai đó có kỹ năng MSIL có thể làm sáng tỏ những gì đang xảy ra dưới mui xe.

Cũng thú vị là nếu bạn thay thế các nhà điều hành coalescing null với đó là tương đương:

_foo = foo != null ? foo : new Foo(); 

Sau đó, nó không thể hiện hành vi này.

Tôi không phải là một assembly/MSIL anh chàng, nhưng chỉ cần dùng một cái nhìn tại đầu ra dissasembly giữa hai phiên bản là thú vị:

 _foo = foo ?? new Foo(); 
0000002d mov   rax,qword ptr [rsp+68h] 
00000032 mov   qword ptr [rsp+28h],rax 
00000037 mov   rax,qword ptr [rsp+60h] 
0000003c mov   qword ptr [rsp+30h],rax 
00000041 cmp   qword ptr [rsp+68h],0 
00000047 jne   0000000000000078 
00000049 lea   rcx,[FFFE23B8h] 
00000050 call  000000005F2E8220 
     var f = _foo; 
00000055 mov   qword ptr [rsp+38h],rax 
0000005a mov   rax,qword ptr [rsp+38h] 
0000005f mov   qword ptr [rsp+40h],rax 
00000064 mov   rcx,qword ptr [rsp+40h] 
00000069 call  FFFFFFFFFFFCA000 
0000006e mov   r11,qword ptr [rsp+40h] 
00000073 mov   qword ptr [rsp+28h],r11 
00000078 mov   rcx,qword ptr [rsp+30h] 
0000007d add   rcx,8 
00000081 mov   rdx,qword ptr [rsp+28h] 
00000086 call  000000005F2E72A0 
0000008b mov   rax,qword ptr [rsp+60h] 
00000090 mov   rax,qword ptr [rax+8] 
00000094 mov   qword ptr [rsp+20h],rax 

Hãy so sánh đó đến inlined-nếu phiên bản:

 _foo = foo != null ? foo : new Foo(); 
0000002d mov   rax,qword ptr [rsp+50h] 
00000032 mov   qword ptr [rsp+28h],rax 
00000037 cmp   qword ptr [rsp+58h],0 
0000003d jne   0000000000000066 
0000003f lea   rcx,[FFFE23B8h] 
00000046 call  000000005F2E8220 
0000004b mov   qword ptr [rsp+30h],rax 
00000050 mov   rax,qword ptr [rsp+30h] 
00000055 mov   qword ptr [rsp+38h],rax 
0000005a mov   rcx,qword ptr [rsp+38h] 
0000005f call  FFFFFFFFFFFCA000 
00000064 jmp   0000000000000070 
00000066 mov   rax,qword ptr [rsp+58h] 
0000006b mov   qword ptr [rsp+38h],rax 
00000070 nop 
00000071 mov   rcx,qword ptr [rsp+28h] 
00000076 add   rcx,8 
0000007a mov   rdx,qword ptr [rsp+38h] 
0000007f call  000000005F2E72A0 
     var f = _foo; 
00000084 mov   rax,qword ptr [rsp+50h] 
00000089 mov   rax,qword ptr [rax+8] 
0000008d mov   qword ptr [rsp+20h],rax 

Dựa trên điều này, tôi nghĩ rằng có một số loại thực hiện trì hoãn xảy ra. Câu lệnh gán trong ví dụ thứ hai là rất nhỏ so với ví dụ đầu tiên.

+3

Điều này dường như là một vấn đề với trình gỡ lỗi 64 bit. Xây dựng và biên dịch nhắm mục tiêu "Bất kỳ CPU" nào thể hiện hành vi này. Thay đổi nó để nhắm mục tiêu x86, sau đó nó không còn là một vấn đề. –

+3

Tôi nghĩ rằng tôi biết vấn đề là gì ... số dòng được tạo bởi VS trong bản dựng 64 bit là không chính xác. Số dòng được tạo cho dòng theo sau đường kết hợp rỗng thực sự là "ở giữa" của lệnh rỗng kết hợp. Vì vậy, trình gỡ lỗi phá vỡ trên dòng đó nhưng lệnh trước đó chưa hoàn thành đầy đủ. Bước qua dòng đó sẽ hoàn thành hướng dẫn đó. Bạn có thể thấy điểm mà tại đó biến thành viên được đặt nếu bạn thực hiện bước tháo gỡ. –

+0

@JeffMercado - Đã xác nhận. Nó chỉ làm điều này với "Bất kỳ CPU" hoặc "x64". Hoạt động tốt với "x86". –

1

Người khác đã gặp phải sự cố tương tự trong this question. Điều thú vị là nó cũng đang sử dụng định dạng this._field = expression ?? new ClassName();. Nó có thể là một loại vấn đề với trình gỡ rối, khi viết giá trị ra dường như tạo ra kết quả chính xác cho chúng.

Hãy thử thêm mã gỡ lỗi/đăng nhập để hiển thị giá trị của trường sau khi gán để loại bỏ sự kỳ quặc trong trình gỡ rối đính kèm.

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