Cuộc gọi phương thức nếu không null trong C #


106

Có thể bằng cách nào đó để rút ngắn câu lệnh này?

if (obj != null)
    obj.SomeMethod();

bởi vì tôi tình cờ viết điều này rất nhiều và nó khá khó chịu. Điều duy nhất tôi có thể nghĩ đến là triển khai mẫu Null Object , nhưng đó không phải là điều tôi có thể làm mọi lúc và chắc chắn đó không phải là giải pháp để rút ngắn cú pháp.

Và vấn đề tương tự với các sự kiện, ở đâu

public event Func<string> MyEvent;

và sau đó gọi

if (MyEvent != null)
    MyEvent.Invoke();

41
Chúng tôi đã cân nhắc việc thêm một toán tử mới vào C # 4: "obj.?SomeMethod ()" có nghĩa là "gọi SomeMethod nếu obj không phải là null, nếu không, trả về null". Thật không may, nó không phù hợp với ngân sách của chúng tôi nên chúng tôi không bao giờ thực hiện nó.
Eric Lippert

@Eric: Nhận xét này còn giá trị không? Tôi đã thấy ở đâu đó rằng, nó có sẵn với 4.0?
CharithJ

@CharithJ: Không. Nó không bao giờ được thực hiện.
Eric Lippert

3
@CharithJ: Tôi biết về sự tồn tại của toán tử liên kết null. Nó không làm những gì Darth muốn; anh ta muốn một toán tử truy cập thành viên được nâng lên thành nullable. (Nhân tiện, nhận xét trước đây của bạn đưa ra mô tả không chính xác về toán tử liên kết null. Bạn muốn nói "v = m == null? Y: m. Giá trị có thể được viết v = m ?? y".)
Eric Lippert

4
Đối với trình đọc mới hơn: C # 6.0 triển khai?., Vì vậy x? .Y? .Z? .OString () sẽ trả về null nếu x, y hoặc z là null hoặc sẽ trả về z.ToString () nếu không có giá trị nào trong số đó là null.
David

Câu trả lời:


162

Từ C # 6 trở đi, bạn chỉ có thể sử dụng:

MyEvent?.Invoke();

hoặc là:

obj?.SomeMethod();

Các ?.là các nhà điều hành null-tuyên truyền, và sẽ gây ra .Invoke()để được ngắn mạch khi các toán hạng lànull . Toán hạng chỉ được truy cập một lần, do đó không có rủi ro về vấn đề "thay đổi giá trị giữa kiểm tra và gọi".

===

Trước C # 6, không: không có phép thuật nào an toàn, với một ngoại lệ; các phương pháp mở rộng - ví dụ:

public static void SafeInvoke(this Action action) {
    if(action != null) action();
}

bây giờ điều này là hợp lệ:

Action act = null;
act.SafeInvoke(); // does nothing
act = delegate {Console.WriteLine("hi");}
act.SafeInvoke(); // writes "hi"

Trong trường hợp sự kiện, điều này có lợi thế là cũng loại bỏ điều kiện chủng tộc, tức là bạn không cần một biến tạm thời. Vì vậy, thông thường bạn cần:

var handler = SomeEvent;
if(handler != null) handler(this, EventArgs.Empty);

nhưng vơi:

public static void SafeInvoke(this EventHandler handler, object sender) {
    if(handler != null) handler(sender, EventArgs.Empty);
}

chúng ta có thể sử dụng đơn giản:

SomeEvent.SafeInvoke(this); // no race condition, no null risk

1
Tôi hơi mâu thuẫn về điều này. Trong trường hợp của một trình xử lý Hành động hoặc sự kiện - thường độc lập hơn - điều này có ý nghĩa. Tuy nhiên, tôi sẽ không phá vỡ tính đóng gói cho một phương thức thông thường. Điều này có nghĩa việc tạo ra các phương pháp trong một lớp tĩnh riêng biệt và tôi không nghĩ rằng mất đóng gói và khả năng đọc suy thoái / tổ chức mã chung là đáng để nâng cấp nho nhỏ trong khả năng đọc địa phương
tvanfosson

@tvanfosson - thật vậy; nhưng quan điểm của tôi là đây là trường hợp duy nhất tôi biết nó sẽ hoạt động ở đâu. Và câu hỏi tự nó nêu lên chủ đề của các đại biểu / sự kiện.
Marc Gravell

Đoạn mã đó kết thúc bằng việc tạo ra một phương thức ẩn danh ở đâu đó, điều này thực sự làm rối tung dấu vết ngăn xếp của bất kỳ ngoại lệ nào. Có thể đặt tên phương thức ẩn danh không? ;)
sisve

Sai theo 2015 / C # 6.0 ...? anh ấy là soltion. ReferenceTHatMayBeNull? .CallMethod () sẽ không gọi phương thức khi null.
TomTom

1
@mercu nó phải là ?.- trong VB14 trở lên
Marc Gravell

27

Những gì bạn đang tìm kiếm là Null có điều kiện (không phải là "coalescing") điều hành: ?.. Nó có sẵn kể từ C # 6.

Ví dụ của bạn sẽ là obj?.SomeMethod();. Nếu obj là null, không có gì xảy ra. Khi phương thức có các đối số, ví dụ: obj?.SomeMethod(new Foo(), GetBar());các đối số không được đánh giá nếu objlà rỗng, điều này quan trọng nếu việc đánh giá các đối số sẽ có tác dụng phụ.

Và có thể xâu chuỗi: myObject?.Items?[0]?.DoSomething()


1
Cái này thật tuyệt. Cần lưu ý rằng đây là một tính năng C # 6 ... (Điều này sẽ được ngụ ý từ câu lệnh VS2015 của bạn, nhưng vẫn đáng chú ý). :)
Kyle Goode

10

Một phương pháp mở rộng nhanh chóng:

    public static void IfNotNull<T>(this T obj, Action<T> action, Action actionIfNull = null) where T : class {
        if(obj != null) {
            action(obj);
        } else if ( actionIfNull != null ) {
            actionIfNull();
        }
    }

thí dụ:

  string str = null;
  str.IfNotNull(s => Console.Write(s.Length));
  str.IfNotNull(s => Console.Write(s.Length), () => Console.Write("null"));

Hay cách khác:

    public static TR IfNotNull<T, TR>(this T obj, Func<T, TR> func, Func<TR> ifNull = null) where T : class {
        return obj != null ? func(obj) : (ifNull != null ? ifNull() : default(TR));
    }

thí dụ:

    string str = null;
    Console.Write(str.IfNotNull(s => s.Length.ToString());
    Console.Write(str.IfNotNull(s => s.Length.ToString(), () =>  "null"));

Tôi đã cố gắng làm điều đó với các phương thức mở rộng và tôi đã nhận được khá nhiều mã giống nhau. Tuy nhiên, vấn đề với việc triển khai này là nó sẽ không hoạt động với các biểu thức trả về kiểu giá trị. Vì vậy, phải có một phương pháp thứ hai cho điều đó.
orad

4

Sự kiện có thể được khởi tạo bằng một đại biểu mặc định trống không bao giờ bị xóa:

public event EventHandler MyEvent = delegate { };

Không cần kiểm tra null.

[ Cập nhật , cảm ơn Bevan đã chỉ ra điều này]

Tuy nhiên, hãy lưu ý về tác động hiệu suất có thể xảy ra. Một điểm chuẩn vi mô nhanh mà tôi đã thực hiện chỉ ra rằng việc xử lý một sự kiện không có người đăng ký chậm hơn 2-3 lần khi sử dụng mẫu "đại biểu mặc định". (Trên máy tính xách tay lõi kép 2,5GHz của tôi, nghĩa là 279ms: 785ms để huy động 50 triệu sự kiện chưa đăng ký.). Đối với các điểm nóng ứng dụng, đó có thể là một vấn đề cần xem xét.


1
Vì vậy, bạn tránh kiểm tra null bằng cách gọi một đại biểu trống ... một sự hy sinh có thể đo lường được của cả bộ nhớ và thời gian để tiết kiệm một vài lần nhấn phím? YMMV, nhưng với tôi, một sự đánh đổi kém.
Bevan

Tôi cũng đã thấy các điểm chuẩn cho thấy việc triệu tập một sự kiện có nhiều đại biểu đăng ký tham gia sẽ đắt hơn đáng kể so với chỉ một.
Greg D


2

Bài viết này của Ian Griffiths đưa ra hai giải pháp khác nhau cho vấn đề mà ông kết luận là những thủ thuật gọn gàng mà bạn không nên sử dụng.


3
Và nói với tư cách là tác giả của bài viết đó, tôi muốn nói thêm rằng bạn chắc chắn không nên sử dụng nó ngay bây giờ vì C # 6 giải quyết vấn đề này bằng các toán tử điều kiện rỗng (?. Và. []).
Ian Griffiths

2

Phương pháp mở rộng ngừng hoạt động như một phương pháp được đề xuất không thực sự giải quyết các vấn đề với điều kiện chủng tộc, mà là ẩn chúng.

public static void SafeInvoke(this EventHandler handler, object sender)
{
    if (handler != null) handler(sender, EventArgs.Empty);
}

Như đã nêu, mã này tương đương với giải pháp có biến tạm thời, nhưng ...

Vấn đề với cả hai rằng có thể subciber của sự kiện có thể được gọi SAU KHI nó đã hủy đăng ký khỏi sự kiện . Điều này có thể xảy ra vì việc hủy đăng ký có thể xảy ra sau khi cá thể ủy nhiệm được sao chép vào biến tạm thời (hoặc được truyền dưới dạng tham số trong phương thức ở trên), nhưng trước khi ủy quyền được gọi.

Nói chung, hành vi của mã máy khách là không thể đoán trước được trong trường hợp đó: trạng thái thành phần không thể cho phép xử lý thông báo sự kiện. Có thể viết mã máy khách theo cách để xử lý nó, nhưng nó sẽ đặt trách nhiệm không cần thiết cho máy khách.

Cách duy nhất được biết để đảm bảo tính an toàn của luồng là sử dụng câu lệnh khóa cho người gửi sự kiện. Điều này đảm bảo rằng tất cả các đăng ký \ hủy đăng ký \ invocation đều được tuần tự hóa.

Để chính xác hơn, khóa nên được áp dụng cho cùng một đối tượng đồng bộ được sử dụng trong phương thức add \ remove event accessor được mặc định là 'this'.


Đây không phải là điều kiện cuộc đua được đề cập đến. Điều kiện chủng tộc là if (MyEvent! = Null) // MyEvent bây giờ không phải là null MyEvent.Invoke (); // MyEvent bây giờ là null, những điều tồi tệ xảy ra Vì vậy, với điều kiện chủng tộc này, tôi không thể viết một trình xử lý sự kiện không đồng bộ được đảm bảo hoạt động. Tuy nhiên với 'điều kiện chủng tộc' của bạn, tôi có thể viết một trình xử lý sự kiện không đồng bộ được đảm bảo hoạt động. Tất nhiên, các cuộc gọi đến thuê bao và hủy đăng ký cần phải đồng bộ, hoặc mã khóa là bắt buộc.
jyoung

Thật. Phân tích của tôi về vấn đề này được đăng ở đây: blog.msdn.com/ericlippert/archive/2009/04/29/…
Eric Lippert


1

Có thể không tốt hơn nhưng theo tôi dễ đọc hơn là tạo một phương thức mở rộng

public static bool IsNull(this object obj) {
 return obj == null;
}

1
những gì hiện các return obj == nullnghĩa. Những gì nó sẽ trở lại
Hammad Khan

1
Nó có nghĩa là nếu objnullphương thức sẽ trả về true, tôi nghĩ.
Joel

1
Làm thế nào điều này thậm chí trả lời câu hỏi?
Kugel

Trả lời muộn, nhưng bạn có chắc điều này sẽ hoạt động? Bạn có thể gọi một phương thức mở rộng trên một đối tượng nullkhông? Tôi khá chắc rằng điều này không hoạt động với các phương thức riêng của loại, vì vậy tôi nghi ngờ nó cũng sẽ hoạt động với các phương thức mở rộng. Tôi tin rằng cách tốt nhất để kiểm tra xem một đối tượng nullobj is null. Thật không may, để kiểm tra xem một đối tượng không phải là nullyêu cầu gói trong dấu ngoặc đơn, điều này thật đáng tiếc.
natiiix

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.