C # elegancki sposób na sprawdzenie czy właściwość danej nieruchomości jest null

W C# powiedz, że chcesz pobrać wartość z PropertyC w tym przykładzie, a ObjectA, PropertyA i PropertyB mogą być null.

ObjectA.PropertyA.PropertyB.PropertyC

Jak mogę bezpiecznie uzyskać PropertyC z jak najmniejszą ilością kodu?

Teraz sprawdziłbym:

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

Byłoby miło zrobić coś takiego (pseudo-kod).

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

Być może jeszcze bardziej upadł z null-koalescencji centrala.

EDIT początkowo mówiłem, że mój drugi przykład jest podobny do js, ale zmieniłem go na psuedo-code, ponieważ zostało poprawnie zaznaczone, że nie będzie działać w js.

Author: ROMANIA_engineer, 2010-08-12

19 answers

W C # 6 możesz użyć null Operator warunkowy. Więc oryginalny test będzie:

int? value = objectA?.PropertyA?.PropertyB?.PropertyC;
 129
Author: Phillip Ngan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-09-01 08:43:59

Metoda Krótkiego Rozszerzenia:

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);
}

Użycie

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

Ta prosta metoda rozszerzenia i wiele więcej można znaleźć na http://devtalk.net/csharp/chained-null-checks-and-the-maybe-monad/

EDIT:

Po użyciu go przez chwilę myślę, że właściwa nazwa dla tej metody powinna być IfNotNull () zamiast oryginalna z().

 28
Author: Krzysztof Morcinek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-19 17:10:52

Czy możesz dodać metodę do swojej klasy? Jeśli nie, Czy myślałeś o użyciu metod rozszerzeń? Możesz utworzyć metodę rozszerzenia dla swojego typu obiektu o nazwie GetPropC().

Przykład:

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;
    }
}

Użycie:

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

Przy okazji, to zakłada, że używasz. NET 3 lub wyższej.

 16
Author: Sam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-09 18:33:20

Sposób w jaki to robisz jest prawidłowy.

Możesz użyć sztuczki takiej jak ta opisana tutaj, używając wyrażeń Linq:

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

Ale znacznie wolniejsze jest ręczne sprawdzanie każdej właściwości...

 12
Author: Thomas Levesque,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-12 13:06:32

Refaktor do przestrzegania Prawa Demeter

 11
Author: rtalbot,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-12 13:58:09
 10
Author: Colonel Panic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-06 18:31:42

Oczywiście szukasz Nullable Monad :

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

Staje się

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

Zwraca null, jeśli którakolwiek z właściwości nullable jest null; w przeciwnym razie wartość Value.

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

Metody rozszerzenia 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);
    }
}
 8
Author: dtb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-12 14:40:53

Zakładając, że masz puste wartości typów, jedno podejście będzie takie:

var x = (((objectA ?? A.Empty).PropertyOfB ?? B.Empty).PropertyOfC ?? C.Empty).PropertyOfString;
Jestem wielkim fanem C# ale bardzo fajna rzecz w Nowej Javie (1.7?) jest .? operator:
 var x = objectA.?PropertyOfB.?PropertyOfC.?PropertyOfString;
 6
Author: Just another metaprogrammer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-12 14:01:01

Ten kod jest "najmniejszą ilością kodu", ale nie najlepszą praktyką:

try
{
    return ObjectA.PropertyA.PropertyB.PropertyC;
}
catch(NullReferenceException)
{
     return null;
}
 4
Author: Boris Modylevsky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-12 13:55:38

Kiedy muszę tak łączyć wywołania, polegam na stworzonej przeze mnie metodzie pomocniczej, 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);
    }

W Twoim przypadku, użyłbyś go tak:

    int value = ObjectA
        .TryGet(p => p.PropertyA)
        .TryGet(p => p.PropertyB)
        .TryGet(p => p.PropertyC, defaultVal);
 4
Author: Emanuel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-08 17:37:27

Widziałem coś w nowym C # 6.0, to przez użycie?"zamiast sprawdzać

Na przykład zamiast używać

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

Po prostu używasz

var city = person?.contact?.address?.city;
Mam nadzieję, że komuś to pomogło.

Aktualizacja:

Możesz zrobić tak teraz

 var city = (Person != null)? 
           ((Person.Contact!=null)? 
              ((Person.Contact.Address!= null)?
                      ((Person.Contact.Address.City!=null)? 
                                 Person.Contact.Address.City : null )
                       :null)
               :null)
            : null;
 3
Author: iYazee6,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-15 13:03:13

Możesz to zrobić:

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;
    ...
}

A nawet lepiej może być to:

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);
 2
Author: Jeffrey L Whitledge,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-12 14:03:56

Napisałbym własną metodę w typie Właściwości (lub metodę rozszerzenia, jeśli nie jest to Twój typ) używając podobnego wzorca do typu Nullable.

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

   public PropertyBType GetPropertyBOrDefault()
   {
       return PropertyB != null ? PropertyB : defaultValue;
   }
}
 1
Author: Steve Danner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-12 17:38:34

Możesz użyć następującego rozszerzenia i myślę, że jest to naprawdę dobre:

/// <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);
}

I używa się go tak:

int value = Nulify.Get(objectA, x=>x.PropertyA, x=>x.PropertyB, x=>x.PropertyC);
 1
Author: Tony,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-10 07:49:36

Po prostu natknąłem się na ten post.

Jakiś czas temu zaproponowałem na Visual Studio Connect dodanie nowego operatora ???.

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

Wymagałoby to trochę pracy ze strony zespołu framework, ale nie trzeba zmieniać języka, ale po prostu zrobić trochę magii kompilatora. Chodziło o to, aby kompilator zmienił ten kod (składnia nie dozwolone atm)

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

Do tego kodu

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()

Dla sprawdzenia null może to wyglądać

bool isNull = (Order.OrderDetails[0].Product ??? null) == null;
 0
Author: Jürgen Steinblock,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-10 06:56:05

Napisałem metodę, która akceptuje wartość domyślną, oto jak jej użyć:

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

Oto kod:

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);
    }
}
 0
Author: Akira Yamamoto,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-08 15:02:04

To niemożliwe.
ObjectA.PropertyA.PropertyB nie powiedzie się, jeśli ObjectA jest null z powodu null dereferencing, co jest błędem.

if(ObjectA != null && ObjectA.PropertyA ... działa z powodu zwarcia, czyli ObjectA.PropertyA nigdy nie będzie sprawdzane, Jeśli ObjectA jest null.

Pierwszy sposób, który proponujesz, jest najlepszy i najbardziej jasny z intencją. Jeśli coś, możesz spróbować przeprojektować bez konieczności polegania na tak wielu null.

 0
Author: DanDan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-05-25 13:58:09

To podejście jest dość proste, gdy przejdziesz przez 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);
        }
    }

Z użyciem, które może wyglądać następująco:

ObjectA objectA = null;

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

Assert.IsNull(objectA.GetPropertyOrDefault(prop => prop.ObjectB));
 -1
Author: BlackjacketMack,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-03 13:07:23
var result = nullableproperty ?? defaultvalue;

?? (operator null-coalescing) oznacza, że jeśli pierwszym argumentem jest null, zwróć drugi.

 -3
Author: Aridane Álamo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-05-25 14:00:59