9

Tôi đang viết các bài kiểm tra đơn vị cho một ứng dụng web MVC, và tôi đã nhận được các ngoại lệ tham chiếu null vì các đối tượng thử nghiệm giả lập chỉ được khởi tạo một phần. Tôi biết dòng nào đang ném ngoại lệ và trông giống như thế này:Visual Studio có thể cho tôi biết tham chiếu nào đã ném NullReferenceException không?

return Supervisor.RegistrationInformation.Registrations 
    .Any(r => 
     r.RegistrationCountry.IsUSAOrCandada() && 
     (!DatesWorked.Start.HasValue || r.RegistrationDate <= DatesWorked.Start.Value) && 
     (!DatesWorked.End.HasValue || r.RegistrationExpirationDate >= DatesWorked.End.Value) && 
     //... 

Có rất nhiều tham chiếu trong đó và bất kỳ vấn đề nào trong số đó có thể là vấn đề. Tuy nhiên, NullReferenceException chính nó dường như không nắm bắt tham chiếu nào bị nổ. Thực tế là tôi đi qua một lambda trình bày một thách thức khác: Theo như tôi có thể nói, tôi không thể bước qua lambda trong khi gỡ lỗi và xem những thành viên của r là null.

Có cách nào tôi có thể làm một hoặc cả hai trong những cách sau:

  • Có Visual Studio cho tôi biết chính xác những tài liệu tham khảo ném NullReferenceException?
  • Nếu không, có cách nào để làm cho trình gỡ lỗi bước qua biểu thức lambda (hoặc chỉ di chuột qua mọi thứ để xem giá trị của chúng) vì nó đang được đánh giá bởi Any?

Tôi cảm thấy phải có cách để làm những việc này, nhưng tôi dường như không thể tìm thấy nó. Tôi đang ở trên VS2010 Premium, và tôi có Resharper, VS Power Tools, và một vài phần mở rộng khác được cài đặt. Nếu có một add-on thực hiện điều này, tôi sẽ ổn với điều đó.

Edit:

Như Eric Lippert chỉ ra, nó không thể xác định được nguồn gốc của một ngoại lệ NR khi mã đã được biên soạn trong cấu hình phát hành. Tôi chỉ hỏi về làm việc trong chế độ gỡ lỗi. Nếu Visual Studio (hoặc một số mở rộng cho VS) có thể theo dõi nguồn của một tham chiếu trong khi gỡ lỗi, điều đó sẽ trả lời câu hỏi của tôi.

Sửa 2:

Câu hỏi thứ hai - làm thế nào để phá vỡ và bước qua một lambda - đã được trả lời, nhưng tôi vẫn muốn biết nếu có một cách tự động để theo dõi một tham chiếu null.

+0

Không, không có cách nào để thực hiện việc này, ngoại trừ ReSharper có thể trợ giúp. –

+0

Bạn đã thử thay đổi lambda thành một hàm ẩn danh và đặt điểm ngắt bên trong nó? – kol

+0

Bật xử lý ngoại lệ cơ hội đầu tiên (Debug => Exceptions) và ngắt khi NullReferenceException được ném? –

Trả lời

17

Không có cách chung để làm những gì bạn muốn, không. Để hiểu tại sao, hãy suy nghĩ về những gì đang xảy ra khi một ngoại lệ tham chiếu null bị ném. Hãy tưởng tượng rằng bạn là trình biên dịch, và bạn phải phát ra mã để xử lý một cuộc gọi đến abc.Def.Ghi.Jkl(), trong đó abc là một địa phương, Def và Ghi là các trường kiểu tham chiếu, và Jkl là một phương thức. Không có hướng dẫn IL nào có thể làm điều gì đó phức tạp; bạn phải phá vỡ nó. Vì vậy, bạn phát ra mã cho một chương trình tương đương, nơi mọi thứ đơn giản hơn nhiều. Bạn phát ra đoạn chương trình:

temp1 = abc.Def; 
temp2 = temp1.Ghi; 
temp2.Jkl(); 

Giả sử temp2 là null vì Ghi là rỗng.Thực tế đó sẽ không được phát hiện cho đến khi Jkl được gọi, tại thời điểm đó điều ném ngoại lệ không có bất kỳ kiến ​​thức nào về cách khởi tạo temp2. Điều đó đã xảy ra từ lâu, một nano giây trong quá khứ và mã máy không có ký ức về quá khứ; tài liệu tham khảo null không lưu ý một chút về nó nói rằng null đến từ đâu, bất kỳ khi nào bạn nói "a = b + c", số kết quả mười hai không ghi chú cùng với thông báo "Tôi đã tổng của b và c ".

+0

Cảm ơn sự hiểu biết của bạn, Eric, thật tuyệt khi có bạn xung quanh để trả lời những câu hỏi này. Thực tế là tôi đang ở chế độ gỡ lỗi có cho tôi bất kỳ tùy chọn nào ở đây không? Tôi đã khá chắc chắn một xây dựng tối ưu hóa sẽ làm chính xác như bạn mô tả, nhưng tôi nghĩ rằng có thể có một tùy chọn để làm cho trình gỡ lỗi theo dõi những thứ như thế này (với chi phí hiệu suất, không có nghi ngờ). –

+0

@JustinMorgan: Bạn rất được hoan nghênh. Để trả lời câu hỏi tiếp theo của bạn: trình gỡ lỗi những ngày này có rất nhiều tính năng mà tôi không biết! Tôi không phải là người sử dụng năng lượng của trình gỡ rối, đủ kỳ quặc. Với số lượng các tính năng mà điều này có những ngày này, có thể cũng có một chế độ mà bạn có thể ngang ngược thời gian và hỏi "giá trị này có được ở đây như thế nào?" Nếu có, tôi không biết nó là gì. –

+0

Trong khi mã chạy không và không nên lưu ý, jitter chắc chắn có thể tạo một ánh xạ giữa địa chỉ thực thi và lệnh IL (giả sử tối ưu hóa đủ thấp), từ đó nó có thể suy ra biến hoặc biểu thức nào gây ra 'NullReferenceException '. Nó đã giữ bản đồ tương tự cho các mục đích khác. Không có ý tưởng nếu nó thực sự tạo ra bản đồ đặc biệt này, nhưng về nguyên tắc nó sẽ có thể làm điều đó. – CodesInChaos

1

Nếu bạn đặt điểm ngắt tại đây, bạn sẽ có thể kiểm tra từng giá trị trước khi dòng được thực hiện và ngoại lệ được ném. Bạn chỉ cần xem xét một cách có phương pháp từng mục được đăng ký cho đến khi bạn tìm thấy mục Null. Visual Studio là rất tốt ở loại điều này.

+0

Vấn đề của tôi đã có thể phá vỡ bên trong biểu thức lambda, hoặc lý tưởng để có trình gỡ lỗi chỉ cho tôi biết thủ phạm để tôi có thể tiết kiệm thời gian. ** Nhận xét của Joe White ** về câu hỏi đã cho tôi biết cách phá vỡ bên trong lambda. –

2

Một giải pháp cho vấn đề cụ thể của bạn là viết lại lambda trong một số nhiều để đánh giá từng điều kiện một và trả về một cách rõ ràng. Sau đó bạn có thể dễ dàng theo dõi qua nó và tìm tham chiếu null.

+1

Hai đoạn đầu tiên của câu hỏi này là vô nghĩa, nhưng câu cuối cùng là một gợi ý hợp lý. "Bất kỳ" không phải là lười biếng. – mquander

+1

Wow. Tôi không biết mình đang nghĩ gì. 'Bất kỳ()' trong một foreach? Pfffftt. Tôi sửa nó rồi. Cảm ơn. – Coincoin

+0

Tôi coi đây là một tùy chọn và sẽ giảm trở lại nếu tôi cuối cùng không tìm thấy mã sự cố. Tôi đã tự hỏi nếu nó có thể bước bên trong lambda mà không làm điều này (mà không bật ra được có thể, xem ** Joe White ** 's bình luận theo câu hỏi). –

1

Bạn có thể đặt điểm ngắt bên trong biểu thức lambda và khi nó chạm, bạn sẽ có thể di chuột qua biểu thức và thấy giá trị của chúng tốt.

Nhìn vào mã của bạn, tôi có thể thấy rằng chỉ một trong ba biểu thức có thể đã gây ra NullRef- r, r.RegistrationCountryDatesWorked. Đặt hai biểu thức đó vào cửa sổ Xem của bạn và yêu cầu trình gỡ lỗi phá vỡ bất kỳ NullReferenceException nào (thông qua Debug-> Exceptions), hoặc đặt điểm ngắt bên trong biểu thức lambda, và biến nó thành điểm ngắt có điều kiện theo điều kiện r == null || r.RegistrationCountry == null || DatesWorked == null và câu trả lời sẽ xuất hiện khá nhanh.

+0

Vào thời điểm tôi đăng câu hỏi tôi không biết làm thế nào để có được một breakpoint bên trong một biểu thức lambda, mặc dù phần cụ thể đã được trả lời. Tuy nhiên, các điểm ngắt có điều kiện và ý tưởng phá vỡ NRE đều là những gợi ý tốt. +1. –

1

Tôi thường không đưa ra câu trả lời không trả lời, nhưng tôi nghĩ rằng câu trả lời cho biết không có cách chung để làm điều này là không chính xác. Dường như với tôi như bạn có thể viết một hàm bao bọc lấy một cây biểu thức, phân tách từng biểu thức con có thuộc tính và trường truy cập, và xây dựng lại nó với các kiểm tra rỗng rõ ràng để ném một ngoại lệ thông tin trên mỗi người truy cập đó. Pseudocode:

static Expression<Func<T, bool>> WithNullDebugging(Expression<Func<T, bool>> exp) 
{ 
    for each node in the expression tree 
     if node is a field, property, or method accessor 
      generate a null check for this member and an exception throw 
      substitute the checked node for this node 
     else if the node has subexpression children 
      call this method recursively on each child 
      substitute each checked subexpression for the subexpression 

    return the fixed expression tree 
} 

Tôi thường không lập trình meta trong C#, vì vậy tôi không chắc chắn, nhưng tôi nghĩ điều này là hoàn toàn có thể; ai đó thông minh cho tôi biết nếu không, và tôi sẽ xóa hoặc sửa câu trả lời này.

+0

Tôi thích ý tưởng này. Nó giống như một phần mở rộng gỡ lỗi cho các biểu thức lambda. Vấn đề cụ thể này đã được giải quyết, nhưng nếu tôi có thể sử dụng một cái gì đó như thế này trong tương lai, tôi sẽ ghi nhớ nó. Cảm ơn. –

1

Vấn đề trước mắt là lambda đang gói rất nhiều logic phức tạp lên trong một câu lệnh duy nhất, vì vậy bạn không thể tìm thấy nơi xảy ra sự cố.

Nhưng đó chỉ là một tác dụng phụ. Vấn đề thực sự là mã của bạn được giả định, không chính xác, rằng không có tham chiếu nào sẽ là rỗng.

Một cách tiếp cận sẽ là cố gắng khắc phục sự cố và đặt băng trên "bit bị hỏng". Nhưng điều đó sẽ không tấn công gốc rễ của vấn đề: có những giả định không được kiểm soát trong mã, và bạn đã có bằng chứng rằng ít nhất một trong số chúng là sai. Nếu một số khác là sai, sau đó tại một số điểm không xác định trong tương lai, chương trình của bạn có thể sẽ sụp đổ một lần nữa, và bạn sẽ gỡ lỗi và băng lại. Điều này có thể tiếp tục và mã của bạn sẽ bị tấn công mọi lúc.

Bạn cần gỡ xuống trình gỡ lỗi và suy nghĩ về mã. Tất cả mã, trong một lần. "Bàn-kiểm tra" nó: chạy qua từng phần của biểu thức và tự hỏi mình "Có thể bit này là null? Điều gì sẽ xảy ra nếu nó? Và nếu vậy, làm thế nào tôi có thể làm cho nó an toàn?"

Bằng cách đó, bạn sẽ có thể viết lại toàn bộ biểu thức trong biểu mẫu mà bạn biết là không nhận thức được và bạn sẽ không bao giờ cần phải gỡ lỗi vào biểu mẫu đó để giải thích lý do tại sao nó bị thổi.

Ví dụ này:

r.RegistrationCountry.IsUSAOrCandada() && 

... có thể gây ra một dereference null nếu r == null hoặc nếu r.RegistrationCountry == null.Mã cần kiểm tra các khả năng này. Mã "phòng thủ nhất" sẽ kiểm tra mọi tham chiếu như sau:

r != null && r.RegistrationCountry != null && r.RegistrationCountry.IsUSAOrCandada() && 

đảm bảo rằng mỗi bước sẽ chỉ được thực hiện nếu bước trước đó thành công. Lưu ý rằng danh sách có thể không bao giờ cung cấp r == null, vì vậy việc kiểm tra có thể không cần thiết. Hoặc r.RegistrationCountry có thể là một cấu trúc (một loại không nullable), vì vậy bạn sẽ biết rằng kiểm tra là không được yêu cầu. Vì vậy, bạn có thể tránh các kiểm tra không cần thiết bằng cách suy nghĩ về nó. Nhưng bạn cần phải suy nghĩ qua từng phần của mã để thách thức và loại bỏ tất cả các giả định.

3

này sẽ không giải quyết toàn bộ vấn đề của bạn, nhưng nó sẽ giúp:

Bạn có thể đặt một breakpoint trong lambda - chỉ cần không theo cách thông thường (cách nhấp vào máng xối sẽ breakpoint báo cáo kết quả chứa, không bên trong của lambda). Bạn phải đặt con trỏ bên trong lambda và nhấn F9 - sau đó bạn sẽ nhận được một breakpoint bên trong lambda của bạn.