2009-12-16 29 views
5

Giả sử tôi muốn kiểm tra một loạt các đối tượng để đảm bảo không có là null:Tôi có thể buộc ngắn mạch của riêng mình trong một cuộc gọi phương thức không?

if (obj != null && 
    obj.Parameters != null && 
    obj.Parameters.UserSettings != null) { 

    // do something with obj.Parameters.UserSettings 
} 

Đó là một viễn cảnh hấp dẫn để viết một hàm helper để chấp nhận một số biến của tham số và đơn giản hóa loại séc:

static bool NoNulls(params object[] objects) { 
    for (int i = 0; i < objects.Length; i++) 
     if (objects[i] == null) return false; 

    return true; 
} 

Sau đó, đoạn mã trên có thể trở thành:

if (NoNulls(obj, obj.Parameters, obj.Parameters.UserSettings)) { 
    // do something 
} 

phải không? Sai. Nếu obj là không, thì tôi sẽ nhận được NullReferenceException khi tôi cố gắng chuyển obj.Parameters đến NoNulls.

Vì vậy, cách tiếp cận ở trên rõ ràng là sai lầm. Nhưng tuyên bố if bằng cách sử dụng toán tử && chỉ hoạt động tốt vì nó được đoản mạch. Vì vậy: là có cách nào để thực hiện một phương pháp ngắn mạch, để các đối số của nó không được đánh giá cho đến khi được tham chiếu một cách rõ ràng trong phương thức?

+11

Những gì bạn thực sự cần để giải quyết vấn đề này không phải là đánh giá lười biếng của các đối số phương pháp - mặc dù, như bạn lưu ý, điều đó sẽ làm các trick. Điều gì sẽ tốt hơn là một toán tử x.?y có ngữ nghĩa của (x == null? Null: x.y) - theo cách đó bạn có thể nói "if (obj.?Parameters.?UserSettings == null)". Chúng tôi coi như một nhà điều hành cho C# 4 nhưng không bao giờ thực hiện nó. Có lẽ cho một phiên bản tương lai giả định. –

+0

Điều đó thực sự thú vị. Có bất kỳ ngôn ngữ nào khác triển khai toán tử tương đương không? –

+1

@Chris: Groovy. Nó được gọi là toán tử dereferencing null-safe. Thật tốt khi biết rằng nhóm C# đã xem xét và có thể xem xét lại lần nữa :) –

Trả lời

9

Vâng, điều này là xấu xí nhưng ...

static bool NoNulls(params Func<object>[] funcs) { 
    for (int i = 0; i < funcs.Length; i++) 
     if (funcs[i]() == null) return false; 

    return true; 
} 

Sau đó, gọi nó với:

if (NoNulls(() => obj, 
      () => obj.Parameters, 
      () => obj.Parameters.UserSettings)) { 
    // do something 
} 

Về cơ bản bạn đang cung cấp các đại biểu để đánh giá giá trị uể oải, chứ không phải là giá trị bản thân (khi đánh giá các giá trị đó là nguyên nhân gây ra ngoại lệ).

Tôi không nói rằng đó là đẹp, nhưng nó ở đó như một tùy chọn ...

EDIT: Đây thực sự (và vô tình) được cho là trung tâm của những gì Dan là sau đó, tôi nghĩ. Tất cả các đối số của phương thức được đánh giá trước khi chính phương thức đó được thực thi. Sử dụng các đại biểu có hiệu quả cho phép bạn trì hoãn việc đánh giá đó cho đến khi phương thức cần gọi cho đại biểu để lấy giá trị.

+4

Đó là xấu xí và khó hiểu, nhưng không phải là nhà điều hành hợp nhất null làm các trick: 'if ((đối tượng z = a ?? b ?? c)! = Null) DoSomething();' – LBushkin

+0

Một ý tưởng khác là để vượt qua một tuyên bố lambda đơn như một cây biểu thức cho một phương thức có thể tách rời cây biểu thức và thực hiện đánh giá ngắn mạch. – LBushkin

+0

Tôi thực sự nghĩ về điều đó, tin hay không.Tất nhiên, với mục đích cho trước - kiểm tra null - điều này sẽ yêu cầu xấp xỉ cùng một lượng mã như cách tiếp cận ban đầu và do đó không phải tất cả đều hữu ích. Đương nhiên, cho các mục đích khác nó có thể tốt hơn. Lý do chính tôi không muốn sử dụng nó, mặc dù, là nó đòi hỏi việc sử dụng lambdas, mà không có sẵn cho VB (ít nhất là trước khi VB 10, tôi nghĩ?), Và tôi muốn cái gì đó sẽ hữu ích từ cả C# và VB, vì chúng tôi sử dụng cả hai ngôn ngữ tại nơi làm việc. –

1

Bạn có thể viết hàm chấp nhận cây biểu thức và biến đổi cây đó thành biểu mẫu sẽ kiểm tra giá trị rỗng và trả lại Func<bool> có thể được đánh giá an toàn để xác định xem có giá trị không.

Tôi nghi ngờ rằng trong khi mã kết quả có thể được làm mát, nó sẽ gây nhầm lẫn và ít hoạt động hơn là chỉ viết một loạt các kiểm tra ngắn mạch a != null && a.b != null.... Trong thực tế, nó có thể sẽ ít hiệu quả hơn là chỉ kiểm tra tất cả các giá trị và bắt các NullReferenceException (không phải là tôi ủng hộ cho xử lý ngoại lệ như một dòng chảy của cơ chế kiểm soát).

Chữ ký cho một chức năng như vậy sẽ giống như thế:

public static Func<bool> NoNulls(Expression<Func<object>> expr) 

và nó sử dụng sẽ giống như thế:

NoNulls(() => new { a = obj, 
        b = obj.Parameters, 
        c = obj.Parameters.UserSettings })(); 

Nếu tôi nhận được một số thời gian rảnh rỗi, tôi sẽ viết một hàm không chỉ là một biến đổi cây biểu thức và cập nhật bài viết của tôi.Tuy nhiên, tôi chắc chắn rằng Jon Skeet hoặc Mark Gravell có thể viết một chức năng như vậy với một mắt nhắm và một tay phía sau lưng.

Tôi cũng muốn thấy C# triển khai toán tử .? mà Eric ám chỉ. Như một Eric khác (Cartman) có thể nói, đó sẽ là "đá ass".

+1

Bạn không cần phải làm cho nó ba đối số riêng biệt - chỉ cần sử dụng '() => obj.Parameters.UserSettings' và lấy cây biểu thức để kiểm tra kết quả cho null giữa mỗi lời gọi thuộc tính. –

0

Bạn có thể sử dụng phản xạ nếu bạn không nhớ mất an toàn loại tĩnh. Tôi nhớ, vì vậy tôi chỉ sử dụng công trình mạch ngắn đầu tiên của bạn. Một tính năng như Eric đã đề cập sẽ được hoan nghênh :)

Tôi đã nghĩ về vấn đề này một vài lần. Lisp có các macro giải quyết vấn đề theo cách bạn đã đề cập, vì chúng cho phép bạn tùy chỉnh đánh giá của mình.

Tôi cũng đã thử sử dụng các phương pháp mở rộng để giải quyết vấn đề này, nhưng không có gì kém xấu hơn mã ban đầu.

Chỉnh sửa: (Trả lời không cho phép tôi chèn khối mã, do đó, chỉnh sửa bài đăng của tôi)

Rất tiếc, đã không theo kịp điều này. Xin lỗi về điều đó :)

Bạn có thể sử dụng phản xạ để tra cứu và đánh giá thành viên hoặc thuộc tính qua chuỗi. Lớp mà một người bạn của tôi đã viết có cú pháp như:

new ReflectionHelper(obj)["Parameters"]["UserSettings"] 

Nó hoạt động thông qua phương pháp chuỗi, trả về ReflectionHelper ở mỗi cấp. Tôi biết rằng NullReferenceException là một vấn đề trong ví dụ đó. Tôi chỉ muốn chứng minh cách đánh giá có thể được trì hoãn cho thời gian chạy.

Một ví dụ gần gũi hơn với hơi bị hữu ích:

public class Something 
{ 
    public static object ResultOrDefault(object baseObject, params string[] chainedFields) 
    { 
    // ... 
    } 
} 

Một lần nữa, cú pháp này stinks. Nhưng điều này thể hiện bằng cách sử dụng chuỗi + phản xạ để trì hoãn việc đánh giá cho thời gian chạy.

+0

Trong sự tò mò, làm thế nào người ta có thể sử dụng sự phản chiếu để đạt được điều này? Vấn đề có vẻ là khi bạn đã nhập phương thức, tất cả các đối số của nó đã được đánh giá. Tôi không rõ ràng về cách phản ánh có thể khắc phục điều này; nhưng có lẽ tôi đang thiếu một cái gì đó. –

+0

Đã chỉnh sửa bài đăng để trả lời nhận xét của bạn –

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