Tại sao C # cho phép bạn 'ném null'?


85

Trong khi viết một số mã xử lý ngoại lệ đặc biệt phức tạp, ai đó đã hỏi, bạn có cần đảm bảo rằng đối tượng ngoại lệ của mình không phải là null không? Và tôi nói, tất nhiên là không, nhưng sau đó quyết định thử nó. Rõ ràng, bạn có thể ném null, nhưng nó vẫn bị biến thành ngoại lệ ở đâu đó.

Tại sao điều này được cho phép?

throw null;

Trong đoạn mã này, rất may 'ex' không phải là null, nhưng nó có thể là không?

try
{
  throw null;
}
catch (Exception ex)
{
  //can ex ever be null?

  //thankfully, it isn't null, but is
  //ex is System.NullReferenceException
}

8
Bạn có chắc chắn rằng bạn không nhìn thấy một ngoại lệ .NET (tham chiếu null) được đưa lên đầu trang của bạn không?
micahtan

4
Bạn sẽ nghĩ rằng trình biên dịch ít nhất sẽ đưa ra cảnh báo về việc cố gắng trực tiếp "ném null;"
Andy White

Không, tôi không chắc. Khung công tác rất có thể đang cố gắng thực hiện điều gì đó với đối tượng của tôi và khi nó là null, khung công tác sẽ ném ra "Null Reference Exception" .. nhưng cuối cùng, tôi muốn chắc chắn rằng "ex" không bao giờ có thể là null
trích

1
@Andy: Một cảnh báo có thể có ý nghĩa đối với các tình huống khác trong langauge, nhưng đối với throwmột câu lệnh có mục đích là đưa ra một ngoại lệ ngay từ đầu, một cảnh báo không có nhiều giá trị.
Mehrdad Afshari

@Mehrdad - vâng, nhưng tôi nghi ngờ liệu có nhà phát triển nào cố tình "ném null" và muốn xem NullReferenceException hay không. Có thể đó là một lỗi hoàn chỉnh, giống như một phép so sánh = sai trong câu lệnh if.
Andy White

Câu trả lời:


96

Bởi vì đặc tả ngôn ngữ mong đợi một biểu thức kiểu System.Exceptionở đó (do đó, nulllà hợp lệ trong ngữ cảnh đó) và không hạn chế biểu thức này là khác rỗng. Nói chung, không có cách nào nó có thể phát hiện ra giá trị của biểu thức đó có phải là nullhay không. Nó sẽ phải giải quyết vấn đề tạm dừng. Thời gian chạy sẽ phải giải quyết nulltrường hợp nào. Xem:

Exception ex = null;
if (conditionThatDependsOnSomeInput) 
    ex = new Exception();
throw ex; 

Tất nhiên, họ có thể làm cho trường hợp cụ thể của việc ném nullchữ không hợp lệ nhưng điều đó sẽ không giúp ích nhiều, vậy tại sao lại lãng phí không gian đặc tả và giảm tính nhất quán vì ít lợi ích?

Tuyên bố từ chối trách nhiệm (trước khi tôi bị Eric Lippert tát): Đây là suy đoán của riêng tôi về lý do đằng sau quyết định thiết kế này. Tất nhiên, tôi chưa tham gia cuộc họp thiết kế;)


Câu trả lời cho câu hỏi thứ hai của bạn, liệu một biến biểu thức được bắt trong mệnh đề bắt có bao giờ có giá trị không: Trong khi đặc tả C # không nói về việc liệu các ngôn ngữ khác có thể gây ra một nullngoại lệ được truyền hay không, nó xác định cách các ngoại lệ được truyền:

Các điều khoản bắt, nếu có, được kiểm tra theo thứ tự xuất hiện để xác định vị trí xử lý phù hợp cho ngoại lệ. Mệnh đề bắt đầu tiên chỉ định kiểu ngoại lệ hoặc kiểu cơ sở của kiểu ngoại lệ được coi là một kết quả khớp. Một mệnh đề bắt chung được coi là phù hợp với bất kỳ loại ngoại lệ nào. [...]

null, tuyên bố in đậm là sai. Vì vậy, mặc dù hoàn toàn dựa trên những gì đặc tả C # nói, chúng ta không thể nói rằng thời gian chạy cơ bản sẽ không bao giờ ném giá trị rỗng, chúng ta có thể chắc chắn rằng ngay cả khi đúng như vậy, nó sẽ chỉ được xử lý bởi catch {}mệnh đề chung .

Để triển khai C # trên CLI, chúng ta có thể tham khảo thông số kỹ thuật ECMA 335. Tài liệu đó xác định tất cả các ngoại lệ mà CLI ném vào bên trong (không có ngoại lệ nào null) và đề cập rằng các đối tượng ngoại lệ do người dùng xác định được ném bởi throwlệnh. Mô tả cho lệnh đó hầu như giống với throwcâu lệnh C # (ngoại trừ việc nó không hạn chế kiểu của đối tượng System.Exception):

Sự miêu tả:

Lệnh throwném đối tượng ngoại lệ (kiểu O) trên ngăn xếp và làm trống ngăn xếp. Để biết chi tiết về cơ chế ngoại lệ, hãy xem Phân vùng I.
[Lưu ý: Trong khi CLI cho phép ném bất kỳ đối tượng nào, CLS mô tả một lớp ngoại lệ cụ thể sẽ được sử dụng cho khả năng tương tác ngôn ngữ. ghi chú cuối]

Các trường hợp ngoại lệ:

System.NullReferenceExceptionđược ném nếu objnull.

Tính đúng đắn:

CIL đúng đảm bảo rằng đối tượng luôn luôn là một nullhoặc một tham chiếu đối tượng (tức là thuộc loại O).

Tôi tin rằng những điều này là đủ để kết luận các trường hợp ngoại lệ bị bắt là không bao giờ null.


Tôi cho rằng điều tốt nhất có thể làm là cấm các cụm từ rõ ràng như throw null;.
FrustratedWithFormsDesigner

7
Trên thực tế, hoàn toàn có thể xảy ra (trong CLR) một đối tượng được ném ra không kế thừa từ System.Exception. Bạn không thể làm điều đó trong C # theo như tôi biết, nhưng nó có thể được thực hiện thông qua IL, C ++ / CLR, v.v. Xem msdn.microsoft.com/en-us/library/ms404228.aspx để biết thêm thông tin.
technophile

@technophile: Có. Tôi đang nói về ngôn ngữ ở đây. Trong C #, không thể ném một ngoại lệ của các kiểu khác nhưng có thể bắt nó bằng cách sử dụng catch { }mệnh đề chung .
Mehrdad Afshari

1
Mặc dù sẽ không hợp lý khi trình biên dịch từ chối chấp nhận một throwđối số có thể là giá trị rỗng, nhưng điều đó sẽ không có nghĩa là điều đó throw nullsẽ phải hợp pháp. Một trình biên dịch có thể nhấn mạnh rằng một throwđối số có một kiểu lớp rõ ràng. Một biểu thức như (System.InvalidOperationException)nullphải hợp lệ tại thời điểm biên dịch (thực thi nó phải gây ra a NullReferenceException) nhưng điều đó không có nghĩa là một biểu thức không định kiểu nullsẽ được chấp nhận.
supercat 13/12/12

@supercat: Đúng vậy, nhưng giá trị null trong trường hợp này được nhập ngầm là Ngoại lệ (vì nó được sử dụng trong ngữ cảnh yêu cầu Ngoại lệ). null không hợp lệ trong thời gian chạy, vì vậy NullReferenceException được ném (như đã nêu trong câu trả lời của Anon.). Ngoại lệ được ném không phải là ngoại lệ trong câu lệnh 'ném'.
John B. Lambe

29

Rõ ràng, bạn có thể ném null, nhưng nó vẫn bị biến thành ngoại lệ ở đâu đó.

Cố gắng ném một nullđối tượng dẫn đến một Ngoại lệ Tham chiếu Null (hoàn toàn không liên quan).

Hỏi tại sao bạn được phép ném nullcũng giống như hỏi tại sao bạn được phép làm điều này:

object o = null;
o.ToString();

5

Lấy từ đây :

Nếu bạn sử dụng biểu thức này trong mã C #, nó sẽ ném ra một NullReferenceException. Đó là bởi vì câu lệnh ném cần một đối tượng kiểu Exception làm tham số duy nhất của nó. Nhưng đối tượng này là rỗng trong ví dụ của tôi.


5

Mặc dù không thể ném null trong C # vì ném sẽ phát hiện ra điều đó và biến nó thành NullReferenceException, nhưng có thể nhận null ... Tôi tình cờ nhận được điều đó ngay bây giờ, điều này gây ra lỗi của tôi (mà không mong đợi 'ex' là null) gặp phải ngoại lệ tham chiếu null, sau đó dẫn đến ứng dụng của tôi sẽ chết (vì đó là lần bắt cuối cùng).

Vì vậy, trong khi chúng ta không thể ném null từ C #, netherworld có thể ném null, do đó, phần bắt ngoài cùng của bạn (Exception ex) tốt hơn hãy chuẩn bị để nhận nó. Chỉ là FYI.


3
Hấp dẫn. Bạn có thể đăng một số mã hoặc có thể trốn tránh những gì nằm sâu bên dưới của bạn đang làm điều này?
Peter Lillevold 17/12/12

Tôi không thể cho bạn biết đó có phải là lỗi CLR hay không ... thật khó để theo dõi những gì trong ruột của .NET đã ném null và tại sao bạn không nhận được Ngoại lệ với ngăn xếp cuộc gọi hoặc bất kỳ manh mối nào khác để tiếp tục. Tôi chỉ biết rằng phần bắt ngoài cùng của tôi hiện đang kiểm tra đối số Ngoại lệ rỗng trước khi sử dụng nó.
Brian Kennedy

3
Tôi nghi ngờ đó là lỗi CLR hoặc do mã không xác minh được. CIL đúng sẽ chỉ ném một đối tượng không phải null hoặc null (trở thành NullReferenceException).
Demi

Tôi cũng đang sử dụng một dịch vụ trả về một ngoại lệ rỗng. Bạn đã bao giờ tìm ra cách mà ngoại lệ null được ném ra chưa?
themiDdlest

2

Tôi nghĩ có lẽ bạn không thể - khi bạn cố gắng ném null, nó không thể, vì vậy nó sẽ làm những gì nó nên trong trường hợp lỗi, đó là ném một ngoại lệ tham chiếu null. Vì vậy, bạn không thực sự ném null, bạn không ném null, dẫn đến ném.


2

Cố gắng trả lời "..cảm ơn 'ex' không phải là null, nhưng nó có thể là không?":

Vì chúng tôi cho rằng không thể ném ra các ngoại lệ là null, một mệnh đề bắt cũng sẽ không bao giờ phải bắt một ngoại lệ là null. Do đó, ex không bao giờ có thể là null.

Tôi thấy rằng câu hỏi này trên thực tế đã được đặt ra .


@Brian Kennedy cho chúng tôi biết trong nhận xét của anh ấy ở trên rằng chúng tôi có thể bắt được null.
ProfK 17/12/12

@ Tvde1 - Tôi nghĩ sự khác biệt ở đây là, vâng, bạn có thể thực thi throw null. Nhưng điều đó sẽ không ném ra một ngoại lệ là null . Thay vào đó, vì throw nullcố gắng gọi các phương thức trên một tham chiếu null, điều này sau đó buộc thời gian chạy phải ném một thể hiện không phải null của NullReferenceException.
Peter Lillevold

1

Hãy nhớ rằng một ngoại lệ bao gồm các chi tiết về nơi mà ngoại lệ được đưa ra. Xem như hàm tạo không biết nó sẽ được ném ở đâu, thì phương thức ném chỉ có ý nghĩa là đưa những chi tiết đó vào đối tượng tại điểm ném. Nói cách khác, CLR đang cố gắng đưa dữ liệu vào null, điều này sẽ kích hoạt NullReferenceException.

Không chắc liệu đây có phải chính xác những gì đang xảy ra hay không, nhưng nó giải thích hiện tượng.

Giả sử điều này là đúng (và tôi không thể nghĩ ra cách nào tốt hơn để khiến ex trở thành null hơn là ném null;), điều đó có nghĩa là ex không thể là null.


0

Trong c # cũ hơn:

Hãy xem xét cú pháp này:

public void Add<T> ( T item ) => throw (hashSet.Add ( item ) ? null : new Exception ( "The item already exists" ));

Tôi nghĩ nó ngắn hơn thế này:

public void Add<T> ( T item )
{
    if (!hashSet.Add ( item ))
        throw new Exception ( "The item already exists" );
}

Điều này cho chúng ta biết điều gì?
Bornfromanegg

Ngẫu nhiên, tôi không chắc định nghĩa của bạn về ngắn hơn là gì, nhưng ví dụ thứ hai của bạn chứa ít ký tự hơn.
Bornfromanegg

@bornfromanegg không có chữ đầu tiên được nội dòng, vì vậy nó chắc chắn ngắn hơn. Điều tôi đang cố gắng nói là bạn có thể mắc lỗi tùy thuộc vào điều kiện. Theo truyền thống, bạn sẽ làm như ví dụ thứ 2, nhưng vì "throw null" không ném bất cứ thứ gì, bạn có thể inline ví dụ thứ hai để giống ví dụ thứ nhất. Ngoài ra, ví dụ đầu tiên có 8 ký tự ít hơn ví dụ thứ 2
Arutyun Enfendzhyan

"Ném null" ném một ngoại lệ NullReference. Có nghĩa là ví dụ đầu tiên của bạn sẽ luôn ném ra một ngoại lệ.
Bornfromanegg

vâng, không may là bây giờ nó không. Nhưng trước đây thì không
Arutyun Enfendzhyan
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.