C # cách tốt để kiểm tra xem thuộc tính của một thuộc tính có rỗng không


97

Trong C #, giả sử rằng bạn muốn lấy một giá trị của PropertyC trong ví dụ này và ObjectA, PropertyA và PropertyB đều có thể là rỗng.

Đối tượngA.PropertyA.PropertyB.PropertyC

Làm cách nào tôi có thể nhận được PropertyC một cách an toàn với số lượng mã ít nhất?

Ngay bây giờ tôi sẽ kiểm tra:

if(ObjectA != null && ObjectA.PropertyA !=null && ObjectA.PropertyA.PropertyB != null)
{
    // safely pull off the value
    int value = objectA.PropertyA.PropertyB.PropertyC;
}

Sẽ rất tuyệt nếu làm một cái gì đó giống như thế này hơn (mã giả).

int value = ObjectA.PropertyA.PropertyB ? ObjectA.PropertyA.PropertyB : defaultVal;

Thậm chí có thể sụp đổ hơn nữa với một toán tử kết hợp không.

CHỈNH SỬA Ban đầu tôi nói ví dụ thứ hai của tôi giống như js, nhưng tôi đã đổi nó thành mã psuedo vì nó được chỉ ra một cách chính xác rằng nó sẽ không hoạt động trong js.


tôi không thấy ví dụ js của bạn hoạt động như thế nào. bạn sẽ nhận được lỗi "đối tượng được mong đợi" bất cứ khi nào ObjectAhoặc PropertyArỗng.
lincolnk

Câu trả lời:


117

Trong C # 6, bạn có thể sử dụng Toán tử Điều kiện Null . Vì vậy, thử nghiệm ban đầu sẽ là:

int? value = objectA?.PropertyA?.PropertyB?.PropertyC;

2
bạn có thể giải thích điều này làm gì? Điều gì valuebằng nếu PropertyClà null? hoặc nếu PropertyBlà null? nếu Object Alà null thì sao?
Kolob Canyon

1
Nếu BẤT KỲ thuộc tính nào trong số này là null, thì toàn bộ câu lệnh trả về là null. Nó bắt đầu từ trái sang phải. Nếu không có đường cú pháp này là tương đương với một loạt các câu lệnh if nơi if(propertyX == null) {value = null} else if (propertyY == null){ value = null} else if......với cuối cùng biểu hiện cuối cùng hạnh phúcif(propertyZ != null) { value = propertyZ }
DetectivePikachu

27

Phương pháp gia hạn ngắn:

public static TResult IfNotNull<TInput, TResult>(this TInput o, Func<TInput, TResult> evaluator)
  where TResult : class where TInput : class
{
  if (o == null) return null;
  return evaluator(o);
}

Sử dụng

PropertyC value = ObjectA.IfNotNull(x => x.PropertyA).IfNotNull(x => x.PropertyB).IfNotNull(x => x.PropertyC);

Phương pháp mở rộng đơn giản này và nhiều hơn nữa bạn có thể tìm thấy trên http://devtalk.net/csharp/chained-null-checks-and-the-maybe-monad/

BIÊN TẬP:

Sau khi sử dụng nó trong giây lát, tôi nghĩ tên riêng cho phương thức này phải là IfNotNull () thay vì With () ban đầu.


15

Bạn có thể thêm một phương thức vào lớp của mình không? Nếu chưa, bạn đã nghĩ đến việc sử dụng các phương pháp mở rộng chưa? Bạn có thể tạo một phương thức mở rộng cho loại đối tượng của bạn được gọi là GetPropC().

Thí dụ:

public static class MyExtensions
{
    public static int GetPropC(this MyObjectType obj, int defaltValue)
    {
        if (obj != null && obj.PropertyA != null & obj.PropertyA.PropertyB != null)
            return obj.PropertyA.PropertyB.PropertyC;
        return defaltValue;
    }
}

Sử dụng:

int val = ObjectA.GetPropC(0); // will return PropC value, or 0 (defaltValue)

Nhân tiện, điều này giả sử bạn đang sử dụng .NET 3 trở lên.


11

Cách bạn đang làm là đúng.

Bạn có thể sử dụng một thủ thuật như được mô tả ở đây , sử dụng biểu thức Linq:

int value = ObjectA.NullSafeEval(x => x.PropertyA.PropertyB.PropertyC, 0);

Nhưng việc kiểm tra thủ công từng thuộc tính chậm hơn nhiều ...


10

Refactor để tuân theo Định luật Demeter


Tôi không coi một biểu đồ đối tượng chỉ sâu ba cấp là cần tái cấu trúc khi bạn chỉ đọc các thuộc tính. Tôi đồng ý nếu OP muốn gọi một phương thức trên một đối tượng được tham chiếu qua PropertyC nhưng không phải khi đó là một thuộc tính chỉ cần kiểm tra null trước khi đọc. Trong ví dụ này, nó có thể đơn giản như Customer.Address.Country trong đó Quốc gia có thể là một loại tham chiếu chẳng hạn như KeyValuePair. Làm thế nào bạn sẽ cấu trúc lại điều này để ngăn chặn nhu cầu kiểm tra ref rỗng?
Darren Lewis

Ví dụ OP thực sự là 4 sâu. Đề xuất của tôi không phải là xóa các kiểm tra ref rỗng mà là xác định vị trí chúng trong các đối tượng có nhiều khả năng nhất để có thể xử lý chúng đúng cách. Giống như hầu hết các "quy tắc ngón tay cái", có những ngoại lệ nhưng tôi không tin đây là một. Chúng ta có thể đồng ý không?
rtalbot

3
Tôi đồng ý với @rtalbot (tuy nhiên, công bằng mà nói, @Daz Lewis đang đề xuất một ví dụ sâu sắc 4, vì mục cuối cùng là KeyValuePair). Nếu có điều gì đó đang gây rối với đối tượng Khách hàng, thì tôi không biết đối tượng đó có hoạt động kinh doanh gì thông qua hệ thống đối tượng Địa chỉ. Giả sử sau này bạn quyết định rằng KeyValuePair không phải là một ý tưởng tuyệt vời cho thuộc tính Country. Trong trường hợp đó, mã của mọi người phải thay đổi. Đó không phải là một thiết kế tốt.
Jeffrey L Whitledge

10

Cập nhật 2014: C # 6 có một toán tử mới ?.khác nhau được gọi là 'điều hướng an toàn' hoặc 'truyền dẫn null'

parent?.child

Đọc http://blogs.msdn.com/b/jerrynixon/archive/2014/02/26/at-last-c-is-getting-sometimes-called-the-safe-navigation-operator.aspx để biết chi tiết

Đây từ lâu đã là một yêu cầu cực kỳ phổ biến https://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/3990187-add-operator-to-c-?tracking_code=594c10a522f8e9bc987ee4a5e2c0b38d


8

Rõ ràng là bạn đang tìm kiếm Nullable Monad :

string result = new A().PropertyB.PropertyC.Value;

trở thành

string result = from a in new A()
                from b in a.PropertyB
                from c in b.PropertyC
                select c.Value;

Điều này trả về null, nếu bất kỳ thuộc tính nullable nào là null; nếu không, giá trị của Value.

class A { public B PropertyB { get; set; } }
class B { public C PropertyC { get; set; } }
class C { public string Value { get; set; } }

Các phương thức mở rộng LINQ:

public static class NullableExtensions
{
    public static TResult SelectMany<TOuter, TInner, TResult>(
        this TOuter source,
        Func<TOuter, TInner> innerSelector,
        Func<TOuter, TInner, TResult> resultSelector)
        where TOuter : class
        where TInner : class
        where TResult : class
    {
        if (source == null) return null;
        TInner inner = innerSelector(source);
        if (inner == null) return null;
        return resultSelector(source, inner);
    }
}

Tại sao lại có phương thức mở rộng ở đây? Nó không được sử dụng.
Mladen Mihajlovic

1
@MladenMihajlovic: SelectManyphương thức mở rộng được sử dụng theo from ... in ... from ... in ...cú pháp.
dtb

5

Giả sử bạn có các giá trị trống của các loại, một cách tiếp cận sẽ là:

var x = (((objectA ?? A.Empty).PropertyOfB ?? B.Empty).PropertyOfC ?? C.Empty).PropertyOfString;

Tôi là một fan hâm mộ lớn của C # nhưng một điều rất hay trong Java mới (1.7?) Là.? nhà điều hành:

 var x = objectA.?PropertyOfB.?PropertyOfC.?PropertyOfString;

1
Nó thực sự sẽ có trong Java 1.7? Nó được yêu cầu trong C # trong một thời gian dài, nhưng tôi nghi ngờ bao giờ nó sẽ xảy ra ...
Thomas Levesque

Rất tiếc, tôi không có giá trị trống. Mặc dù vậy, cú pháp Java có vẻ ngọt ngào! Tôi sẽ bỏ phiếu này, chỉ vì tôi muốn cú pháp đó!
Jon Kragh

3
Thomas: Lần trước tôi đã kiểm tra tech.puredanger.com/java7, nó ngụ ý rằng Java sẽ nhận được nó. Nhưng bây giờ khi tôi kiểm tra lại nó nói: Xử lý an toàn vô hiệu: KHÔNG. Vì vậy, tôi thu hồi tuyên bố của mình và thay thế nó bằng một tuyên bố mới: Nó được đề xuất cho Java 1.7 nhưng không thành công.
Chỉ cần một metaprogrammer

Một cách tiếp cận khác là một trong những sử dụng bởi monad.net
Just another metaprogrammer

1
Có vẻ như?. có trong Visual Studio 2015 https://msdn.microsoft.com/en-us/library/dn986595.aspx
Edward

4

Mã này là "số lượng mã ít nhất", nhưng không phải là phương pháp hay nhất:

try
{
    return ObjectA.PropertyA.PropertyB.PropertyC;
}
catch(NullReferenceException)
{
     return null;
}

1
Tôi đã thấy mã như thế này rất nhiều và bỏ qua việc mất hiệu suất, vấn đề lớn nhất là nó làm phức tạp việc gỡ lỗi vì ngoại lệ thực sự chìm trong hàng triệu ngoại lệ null ref vô dụng.
Chỉ cần một metaprogrammer

Đôi khi đọc câu trả lời của chính tôi sau 3 năm thật thú vị. Tôi nghĩ, hôm nay tôi sẽ trả lời khác. Tôi sẽ nói rằng mã vi phạm luật của Demeter và tôi khuyên bạn nên cấu trúc lại nó để nó không.
Boris Modylevsky

1
Tính đến ngày hôm nay, 7 năm sau câu trả lời ban đầu, tôi tham gia @Phillip Ngan và sẽ sử dụng C # 6 với cú pháp sau: int? value = objectA? .PropertyA? .PropertyB? .PropertyC;
Boris Modylevsky

4

Khi tôi cần chuỗi các cuộc gọi như vậy, tôi dựa vào phương thức trợ giúp mà tôi đã tạo, TryGet ():

    public static U TryGet<T, U>(this T obj, Func<T, U> func)
    {
        return obj.TryGet(func, default(U));
    }

    public static U TryGet<T, U>(this T obj, Func<T, U> func, U whenNull)
    {
        return obj == null ? whenNull : func(obj);
    }

Trong trường hợp của bạn, bạn sẽ sử dụng nó như vậy:

    int value = ObjectA
        .TryGet(p => p.PropertyA)
        .TryGet(p => p.PropertyB)
        .TryGet(p => p.PropertyC, defaultVal);

Tôi không nghĩ rằng mã này hoạt động. Loại defaultVal là gì? var p = new Person (); Assert.AreEqual (p.TryGet (x => x.FirstName) .TryGet (x => x.LastName) .TryGet (x => x.NickName, "foo"), "foo");
Keith

Ví dụ tôi đã viết nên được đọc như sau: ObjectA.PropertyA.PropertyB.PropertyC. Mã của bạn dường như đang cố gắng tải một thuộc tính có tên "LastName" từ "FirstName", đây không phải là mục đích sử dụng. Ví dụ chính xác hơn sẽ là: var postcode = person.TryGet (p => p.Address) .TryGet (p => p.Postcode); Nhân tiện, phương thức trợ giúp TryGet () của tôi rất giống với một tính năng mới trong C # 6.0 - toán tử điều kiện rỗng. Cách sử dụng của nó sẽ như vậy: var postcode = person? .Address? .Postcode; msdn.microsoft.com/en-us/magazine/dn802602.aspx
Emanuel

3

Tôi đã thấy một cái gì đó trong C # 6.0 mới, đây là bằng cách sử dụng '?' thay vì kiểm tra

ví dụ thay vì sử dụng

if (Person != null && Person.Contact!=null && Person.Contact.Address!= null && Person.Contact.Address.City != null)
{ 
  var city = person.contact.address.city;
}

bạn chỉ cần sử dụng

var city = person?.contact?.address?.city;

Tôi hy vọng nó đã giúp ai đó.


CẬP NHẬT:

Bạn có thể làm như thế này bây giờ

 var city = (Person != null)? 
           ((Person.Contact!=null)? 
              ((Person.Contact.Address!= null)?
                      ((Person.Contact.Address.City!=null)? 
                                 Person.Contact.Address.City : null )
                       :null)
               :null)
            : null;

1

Bạn có thể làm điều này:

class ObjectAType
{
    public int PropertyC
    {
        get
        {
            if (PropertyA == null)
                return 0;
            if (PropertyA.PropertyB == null)
                return 0;
            return PropertyA.PropertyB.PropertyC;
        }
    }
}



if (ObjectA != null)
{
    int value = ObjectA.PropertyC;
    ...
}

Hoặc thậm chí tốt hơn có thể là thế này:

private static int GetPropertyC(ObjectAType objectA)
{
    if (objectA == null)
        return 0;
    if (objectA.PropertyA == null)
        return 0;
    if (objectA.PropertyA.PropertyB == null)
        return 0;
    return objectA.PropertyA.PropertyB.PropertyC;
}


int value = GetPropertyC(ObjectA);

1

bạn có thể sử dụng tiện ích mở rộng sau và tôi nghĩ nó thực sự tốt:

/// <summary>
/// Simplifies null checking
/// </summary>
public static TR Get<TF, TR>(TF t, Func<TF, TR> f)
    where TF : class
{
    return t != null ? f(t) : default(TR);
}

/// <summary>
/// Simplifies null checking
/// </summary>
public static TR Get<T1, T2, TR>(T1 p1, Func<T1, T2> p2, Func<T2, TR> p3)
    where T1 : class
    where T2 : class
{
    return Get(Get(p1, p2), p3);
}

/// <summary>
/// Simplifies null checking
/// </summary>
public static TR Get<T1, T2, T3, TR>(T1 p1, Func<T1, T2> p2, Func<T2, T3> p3, Func<T3, TR> p4)
    where T1 : class
    where T2 : class
    where T3 : class
{
    return Get(Get(Get(p1, p2), p3), p4);
}

Và nó được sử dụng như thế này:

int value = Nulify.Get(objectA, x=>x.PropertyA, x=>x.PropertyB, x=>x.PropertyC);

0

Tôi sẽ viết phương thức của riêng bạn trong kiểu PropertyA (hoặc một phương thức mở rộng nếu nó không phải là kiểu của bạn) bằng cách sử dụng mẫu tương tự với kiểu Nullable.

class PropertyAType
{
   public PropertyBType PropertyB {get; set; }

   public PropertyBType GetPropertyBOrDefault()
   {
       return PropertyB != null ? PropertyB : defaultValue;
   }
}

Vâng, trong trường hợp đó, rõ ràng PropertyB không bao giờ có thể là rỗng.
đệ quy

0

Chỉ tình cờ qua bài viết này.

Cách đây một thời gian, tôi đã đưa ra đề xuất trên Visual Studio Connect về việc thêm một ???toán tử mới .

http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/4104392-add-as-an-recursive-null-reference-check-opera

Điều này sẽ yêu cầu một số công việc từ nhóm khung nhưng không cần thay đổi ngôn ngữ mà chỉ cần thực hiện một số phép thuật biên dịch. Ý tưởng là trình biên dịch nên thay đổi mã này (cú pháp không được phép atm)

string product_name = Order.OrderDetails[0].Product.Name ??? "no product defined";

vào mã này

Func<string> _get_default = () => "no product defined"; 
string product_name = Order == null 
    ? _get_default.Invoke() 
    : Order.OrderDetails[0] == null 
        ? _get_default.Invoke() 
        : Order.OrderDetails[0].Product == null 
            ? _get_default.Invoke() 
            : Order.OrderDetails[0].Product.Name ?? _get_default.Invoke()

Đối với kiểm tra null, điều này có thể trông giống như

bool isNull = (Order.OrderDetails[0].Product ??? null) == null;

0

Tôi đã viết một phương thức chấp nhận một giá trị mặc định, đây là cách sử dụng nó:

var teacher = new Teacher();
return teacher.GetProperty(t => t.Name);
return teacher.GetProperty(t => t.Name, "Default name");

Đây là mã:

public static class Helper
{
    /// <summary>
    /// Gets a property if the object is not null.
    /// var teacher = new Teacher();
    /// return teacher.GetProperty(t => t.Name);
    /// return teacher.GetProperty(t => t.Name, "Default name");
    /// </summary>
    public static TSecond GetProperty<TFirst, TSecond>(this TFirst item1,
        Func<TFirst, TSecond> getItem2, TSecond defaultValue = default(TSecond))
    {
        if (item1 == null)
        {
            return defaultValue;
        }

        return getItem2(item1);
    }
}

1
Giải pháp này đã được cung cấp trong các câu trả lời khác (nhiều lần). Không có lý do gì để đăng lại nó .
Servy

Tôi không thấy bất kỳ giá trị nào chấp nhận giá trị mặc định.
Akira Yamamoto

Tôi đếm 6 người khác sử dụng giá trị mặc định đã xác định. Rõ ràng trông bạn không khó đến vậy.
Servy

0

Điều đó là không thể.
ObjectA.PropertyA.PropertyBsẽ không thành công nếu ObjectAlà null do không tham chiếu, đó là một lỗi.

if(ObjectA != null && ObjectA.PropertyA... hoạt động do đoản mạch, tức là ObjectA.PropertyAsẽ không bao giờ được kiểm tra nếu ObjectAnull.

Cách đầu tiên bạn đề xuất là tốt nhất và rõ ràng nhất với ý định. Nếu bất cứ điều gì bạn có thể cố gắng thiết kế lại mà không cần phải dựa vào quá nhiều null.


-1

Cách tiếp cận này khá dễ hiểu khi bạn vượt qua lambda gobbly-gook:

public static TProperty GetPropertyOrDefault<TObject, TProperty>(this TObject model, Func<TObject, TProperty> valueFunc)  
                                                        where TObject : class
    {
        try
        {
            return valueFunc.Invoke(model);
        }
        catch (NullReferenceException nex)
        {
            return default(TProperty);
        }
    }

Với cách sử dụng có thể trông giống như:

ObjectA objectA = null;

Assert.AreEqual(0,objectA.GetPropertyOrDefault(prop=>prop.ObjectB.ObjectB.ObjectC.ID));

Assert.IsNull(objectA.GetPropertyOrDefault(prop => prop.ObjectB));

Chỉ tò mò tại sao ai đó lại phản đối 8 năm sau khi tôi cung cấp phản hồi (đó là nhiều năm trước khi hợp nhất null của C # 6 là một điều).
BlackjacketMack

-3
var result = nullableproperty ?? defaultvalue;

Các ??(nhà điều hành null-coalescing) có nghĩa là nếu đối số đầu tiên là null, trả lại một giây để thay thế.


2
Câu trả lời này không giải quyết được vấn đề của OP. Bạn sẽ áp dụng giải pháp như thế nào với ?? toán tử đối với ObjectA.PropertyA.PropertyB khi tất cả các phần của biểu thức (ObjectA, PropertyA và PropertyB) có thể rỗng?
Artemix

Đúng, tôi nghĩ rằng tôi thậm chí đã không đọc câu hỏi nào cả. Dù sao, không thể là không có gì chỉ cần không làm điều đó: P static void Main (string [] args) {a ca = new a (); var default_value = new a () {b = new object ()}; var value = (ca ?? default_value) .b ?? default_value.b; } class a {public object b = null; }
Aridane Álamo

(ObjectA ?? DefaultMockedAtNull) .PropertyA! = Null? ObjectA.PropertyA.PropertyB: null
Aridane Álamo
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.