Trong C #, điều gì xảy ra khi bạn gọi một phương thức mở rộng trên một đối tượng null?


328

Phương thức này có được gọi với giá trị null hay không hoặc có đưa ra ngoại lệ tham chiếu null không?

MyObject myObject = null;
myObject.MyExtensionMethod(); // <-- is this a null reference exception?

Nếu đây là trường hợp tôi sẽ không bao giờ cần kiểm tra tham số 'this' của mình cho null?


Tất nhiên, trừ khi bạn đang xử lý ASP.NET MVC sẽ gây ra lỗi này Cannot perform runtime binding on a null reference.
Ông trùm

Câu trả lời:


386

Điều đó sẽ làm việc tốt (không có ngoại lệ). Các phương thức mở rộng không sử dụng các cuộc gọi ảo (nghĩa là nó sử dụng lệnh "gọi" il, không phải "callvirt") vì vậy không có kiểm tra null trừ khi bạn tự viết nó trong phương thức mở rộng. Điều này thực sự hữu ích trong một vài trường hợp:

public static bool IsNullOrEmpty(this string value)
{
    return string.IsNullOrEmpty(value);
}
public static void ThrowIfNull<T>(this T obj, string parameterName)
        where T : class
{
    if(obj == null) throw new ArgumentNullException(parameterName);
}

Vân vân

Về cơ bản, các cuộc gọi đến các cuộc gọi tĩnh rất đúng nghĩa - nghĩa là

string s = ...
if(s.IsNullOrEmpty()) {...}

trở thành:

string s = ...
if(YourExtensionClass.IsNullOrEmpty(s)) {...}

nơi rõ ràng là không có kiểm tra null.


1
Marc, bạn đang nói về các cuộc gọi ảo trên mạng - nhưng điều tương tự cũng đúng với các cuộc gọi không ảo trên các phương thức cá thể. Tôi nghĩ rằng từ ảo ảo ở đây bị đặt nhầm chỗ.
Konrad Rudolph

6
@Konrad: Nó phụ thuộc vào bối cảnh. Trình biên dịch C # thường sử dụng callvirt ngay cả đối với các phương thức không ảo, chính xác để có được kiểm tra null.
Jon Skeet

Tôi đã đề cập đến sự khác biệt giữa hướng dẫn cuộc gọi và cuộc gọi il. Trong một lần chỉnh sửa, tôi thực sự đã cố gắng để href hai trang Opcodes, nhưng trình soạn thảo đã chặn các liên kết ...
Marc Gravell

2
Tôi không thấy cách sử dụng các phương thức mở rộng này thực sự hữu ích. Chỉ vì nó có thể được thực hiện không có nghĩa là nó đúng, và như Binary Worrier được đề cập dưới đây, đối với tôi, nó giống như một quang sai để nói ít nhất.
Bẫy

3
@Trap: tính năng này rất tuyệt nếu bạn tham gia lập trình theo kiểu chức năng.
Roy Tinker

50

Ngoài ra câu trả lời đúng từ Marc Gravell.

Bạn có thể nhận được cảnh báo từ trình biên dịch nếu rõ ràng đối số này là null:

default(string).MyExtension();

Hoạt động tốt trong thời gian chạy, nhưng tạo ra cảnh báo "Expression will always cause a System.NullReferenceException, because the default value of string is null".


32
Tại sao nó lại cảnh báo "luôn gây ra System.NullReferenceException". Trong thực tế, nó sẽ không bao giờ?
tpower

46
May mắn thay, chúng tôi lập trình viên chỉ quan tâm đến lỗi, không cảnh báo: p
JulianR

7
@JulianR: Vâng, một số thì không, một số thì không. Trong cấu hình xây dựng bản phát hành, chúng tôi coi các cảnh báo là lỗi. Vì vậy, nó chỉ không hoạt động.
Stefan Steinegger

8
Cảm ơn bạn đã lưu ý; Tôi sẽ nhận được điều này trong cơ sở dữ liệu lỗi và chúng tôi sẽ xem liệu chúng tôi có thể sửa nó cho C # 4.0 không. (Không hứa hẹn - vì đây là một trường hợp góc không thực tế và chỉ là một cảnh báo, chúng tôi có thể tạm dừng sửa nó.)
Eric Lippert

3
@Stefan: Vì đó là một lỗi và không phải là cảnh báo "đúng", bạn có thể sử dụng câu lệnh #pragma để thay thế cảnh báo để lấy mã vượt qua bản dựng phát hành của bạn.
Martin RL

24

Như bạn đã phát hiện ra, vì các phương thức mở rộng chỉ đơn giản là các phương thức tĩnh được tôn vinh, chúng sẽ được gọi với các nulltham chiếu được truyền vào mà không NullReferenceExceptionbị ném. Nhưng, vì chúng trông giống như các phương thức cá thể đối với người gọi, nên chúng cũng nên hành xử như vậy. Sau đó, hầu hết thời gian, bạn nên kiểm tra thistham số và ném ngoại lệ nếu có null. Không nên làm điều này nếu phương thức xử lý rõ ràng các nullgiá trị và tên của nó chỉ ra nó hợp lệ, như trong các ví dụ dưới đây:

public static class StringNullExtensions { 
  public static bool IsNullOrEmpty(this string s) { 
    return string.IsNullOrEmpty(s); 
  } 
  public static bool IsNullOrBlank(this string s) { 
    return s == null || s.Trim().Length == 0; 
  } 
}

Tôi cũng đã viết một bài blog về điều này một thời gian trước đây.


3
Bỏ phiếu này vì nó đúng và có ý nghĩa với tôi (và được viết tốt), trong khi tôi cũng thích cách sử dụng được mô tả trong câu trả lời của @Marc Gravell.
qxotk

17

Một null sẽ được truyền cho phương thức mở rộng.

Nếu phương thức cố gắng truy cập vào đối tượng mà không kiểm tra là null, thì có, nó sẽ đưa ra một ngoại lệ.

Một anh chàng ở đây đã viết các phương thức mở rộng "IsNull" và "IsNotNull" mà kiểm tra xem tham chiếu có được thông qua null hay không. Cá nhân tôi nghĩ rằng đây là một quang sai và không nên nhìn thấy ánh sáng trong ngày, nhưng nó hoàn toàn hợp lệ c #.


18
Thật vậy, với tôi nó giống như hỏi một xác chết "Bạn còn sống" và nhận được câu trả lời là "không". Một xác chết không thể trả lời bất kỳ câu hỏi nào, bạn cũng không thể "gọi" một phương thức trên một đối tượng null.
Nhị phân nhị phân

14
Tôi không đồng ý với logic của Binary Woreller, vì nó có thể gọi các tiện ích mở rộng mà không phải lo lắng về các giới thiệu null, nhưng +1 cho giá trị hài tương tự :-)
Tim Abell

10
Trên thực tế, đôi khi bạn không biết ai đó đã chết, vì vậy bạn vẫn hỏi và người đó có thể trả lời: "không, chỉ cần tôi nhắm mắt lại"
nurchi

7
Khi bạn cần xâu chuỗi một số thao tác (giả sử là 3+), bạn có thể (giả sử không có tác dụng phụ) biến một vài dòng mã kiểm tra null nồi hơi nhàm chán thành một lớp lót được xâu chuỗi một cách thanh lịch với các phương thức mở rộng "không an toàn". (Tương tự như toán tử ".?" - được đề xuất, nhưng phải thừa nhận là không thanh lịch.) Nếu không rõ ràng thì một phần mở rộng là "null-safe", tôi thường đặt tiền tố cho phương thức là "An toàn", vì vậy, ví dụ như đó là một bản sao- phương thức, tên của nó có thể là "SafeCopy" và nó sẽ trả về null nếu đối số là null.
AnorZaken

3
Tôi đã cười rất nhiều với câu trả lời @BinaryWorrier hahahaha tôi thấy mình đang đá một cơ thể để kiểm tra xem nó đã chết hay chưa hahaha Vì vậy, trong trí tưởng tượng của tôi, người đã kiểm tra xem cơ thể đã chết hay không phải là chính tôi, việc thực hiện kiểm tra là trong tôi, kích hoạt nó để xem nếu nó di chuyển. Vì vậy, một cơ thể không biết nó có chết hay không, WHO kiểm tra, biết, bây giờ bạn có thể lập luận rằng bạn có thể "cắm" vào cơ thể một cách để nó nói cho bạn biết nó có chết hay không và theo ý kiến ​​của tôi là gì một phần mở rộng là dành cho.
Zorkind

7

Như những người khác đã chỉ ra, việc gọi một phương thức mở rộng trên tham chiếu null làm cho đối số này là null và sẽ không có gì khác đặc biệt xảy ra. Điều này đưa ra một ý tưởng để sử dụng các phương pháp mở rộng để viết các mệnh đề bảo vệ.

Bạn có thể đọc bài viết này để biết ví dụ: Cách giảm độ phức tạp theo chu kỳ: Điều khoản bảo vệ Phiên bản ngắn là đây:

public static class StringExtensions
{
    public static void AssertNonEmpty(this string value, string paramName)
    {
        if (string.IsNullOrEmpty(value))
            throw new ArgumentException("Value must be a non-empty string.", paramName);
    }
}

Đây là phương thức mở rộng lớp chuỗi có thể được gọi trên tham chiếu null:

((string)null).AssertNonEmpty("null");

Cuộc gọi chỉ hoạt động tốt vì thời gian chạy sẽ gọi thành công phương thức mở rộng trên tham chiếu null. Sau đó, bạn có thể sử dụng phương thức mở rộng này để triển khai các mệnh đề bảo vệ mà không cần cú pháp lộn xộn:

    public IRegisteredUser RegisterUser(string userName, string referrerName)
    {

        userName.AssertNonEmpty("userName");
        referrerName.AssertNonEmpty("referrerName");

        ...

    }

3

Phần mở rộng là tĩnh, vì vậy nếu bạn không làm gì với MyObject này thì đó không phải là vấn đề, một bài kiểm tra nhanh sẽ xác minh nó :)


-1

Có một vài quy tắc vàng khi bạn muốn có thể đọc được và thẳng đứng.

  • một điều đáng nói từ Eiffel nói rằng mã cụ thể được gói gọn trong một phương thức sẽ hoạt động chống lại một số đầu vào, mã đó hoàn toàn khả thi nếu được đáp ứng một số điều kiện tiên quyết và đảm bảo đầu ra dự kiến

Trong trường hợp của bạn - DesignByContract bị hỏng ... bạn sẽ thực hiện một số logic trên một ví dụ null.

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.