C # elegante Methode, um zu überprüfen, ob die Eigenschaft einer Eigenschaft null ist

97

Angenommen, Sie möchten in C # einen Wert von PropertyC in diesem Beispiel abrufen, und ObjectA, PropertyA und PropertyB können alle null sein.

ObjectA.PropertyA.PropertyB.PropertyC

Wie kann ich PropertyC mit der geringsten Menge an Code sicher erhalten?

Im Moment würde ich überprüfen:

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

Es wäre schön, so etwas mehr zu machen (Pseudocode).

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

Möglicherweise ist er mit einem Null-Koaleszenz-Operator noch weiter zusammengebrochen.

BEARBEITEN Ursprünglich sagte ich, mein zweites Beispiel sei wie js, aber ich habe es in Pseudo-Code geändert, da richtig darauf hingewiesen wurde, dass es in js nicht funktionieren würde.

Jon Kragh
quelle
Ich sehe nicht, wie dein js Beispiel funktioniert. Sie sollten immer dann den Fehler "Objekt erwartet" erhalten, wenn ObjectAoder wenn sie PropertyAnull sind.
Lincolnk

Antworten:

117

In C # 6 können Sie den Null-Bedingungsoperator verwenden . Der ursprüngliche Test lautet also:

int? value = objectA?.PropertyA?.PropertyB?.PropertyC;
Phillip Ngan
quelle
2
Können Sie erklären, was dies bewirkt? Was ist valuegleich wenn PropertyCnull ist? oder wenn PropertyBist null? Was ist, wenn Object ANull ist?
Kolob Canyon
1
Wenn eine dieser Eigenschaften null ist, gibt die gesamte Anweisung als zurück null. Es beginnt von links nach rechts. Ohne den syntaktischen Zucker entspricht dies einer Reihe von if-Anweisungen, bei if(propertyX == null) {value = null} else if (propertyY == null){ value = null} else if......denen der letzte Ausdruckif(propertyZ != null) { value = propertyZ }
DetectivePikachu
27

Kurze Verlängerungsmethode:

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

Verwenden von

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

Diese einfache Erweiterungsmethode und vieles mehr finden Sie unter http://devtalk.net/csharp/chained-null-checks-and-the-maybe-monad/

BEARBEITEN:

Nachdem ich es für einen Moment benutzt habe, denke ich, dass der richtige Name für diese Methode IfNotNull () anstelle von original With () sein sollte.

Krzysztof Morcinek
quelle
15

Können Sie Ihrer Klasse eine Methode hinzufügen? Wenn nicht, haben Sie über die Verwendung von Erweiterungsmethoden nachgedacht? Sie können eine Erweiterungsmethode für Ihren Objekttyp mit dem Namen erstellen GetPropC().

Beispiel:

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

Verwendung:

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

Dies setzt übrigens voraus, dass Sie .NET 3 oder höher verwenden.

Sam
quelle
11

Die Art und Weise, wie Sie es tun, ist richtig.

Sie können einen Trick wie den hier beschriebenen mit Linq-Ausdrücken verwenden:

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

Aber es ist viel langsamer, jede Eigenschaft manuell zu überprüfen ...

Thomas Levesque
quelle
10

Refactor zur Einhaltung des Demeter-Gesetzes

rtalbot
quelle
Ich halte ein Objektdiagramm, das nur drei Ebenen tief ist, nicht für umgestaltbar, wenn Sie nur Eigenschaften lesen. Ich würde zustimmen, wenn das OP eine Methode für ein Objekt aufrufen möchte, auf das über PropertyC verwiesen wird, aber nicht, wenn es sich um eine Eigenschaft handelt, die vor dem Lesen nur auf Null überprüft werden muss. In diesem Beispiel könnte es so einfach wie Customer.Address.Country sein, wobei Country ein Referenztyp wie KeyValuePair sein könnte. Wie würden Sie dies umgestalten, um zu verhindern, dass die Null-Ref-Prüfungen erforderlich sind?
Darren Lewis
Das OP-Beispiel ist tatsächlich 4 tief. Mein Vorschlag ist nicht, die Null-Ref-Prüfungen zu entfernen, sondern sie in den Objekten zu lokalisieren, die am wahrscheinlichsten in der Lage sind, sie richtig zu handhaben. Wie bei den meisten "Faustregeln" gibt es Ausnahmen, aber ich bin nicht davon überzeugt, dass dies eine ist. Können wir uns darauf einigen, nicht zuzustimmen?
RTALbot
3
Ich stimme @rtalbot zu (obwohl @Daz Lewis fairerweise ein 4-tiefes Beispiel vorschlägt, da das letzte Element ein KeyValuePair ist). Wenn etwas mit einem Kundenobjekt in Konflikt gerät, sehe ich nicht, welches Geschäft es in der Hierarchie der Adressobjekte hat. Angenommen, Sie entscheiden später, dass ein KeyValuePair für die Country-Eigenschaft keine so gute Idee war. In diesem Fall muss sich jeder Code ändern. Das ist kein gutes Design.
Jeffrey L Whitledge
10

Update 2014: C # 6 hat einen neuen Operator ?.namens "Sichere Navigation" oder "Null-Propagierung".

parent?.child

Weitere Informationen finden Sie unter http://blogs.msdn.com/b/jerrynixon/archive/2014/02/26/at-last-c-is-getting-sometimes-called-the-safe-navigation-operator.aspx

Dies ist seit langem eine äußerst beliebte Anfrage: https://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/3990187-add-operator-to-c-?tracking_code=594c10a522f8e9bc987ee4a5e2c0b38d

Oberst Panik
quelle
8

Sie suchen offensichtlich nach der Nullable Monad :

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

wird

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

Dies gibt zurück null, wenn eine der nullbaren Eigenschaften null ist. Andernfalls wird der Wert von Value.

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

LINQ-Erweiterungsmethoden:

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);
    }
}
dtb
quelle
Warum ist die Erweiterungsmethode hier? Es wird nicht verwendet.
Mladen Mihajlovic
1
@MladenMihajlovic: Die SelectManyErweiterungsmethode wird von der from ... in ... from ... in ...Syntax verwendet.
dtb
5

Angenommen, Sie haben leere Werte vom Typ. Ein Ansatz wäre folgender:

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

Ich bin ein großer Fan von C #, aber eine sehr schöne Sache in neuem Java (1.7?) Ist das.? Operator:

 var x = objectA.?PropertyOfB.?PropertyOfC.?PropertyOfString;
Nur ein weiterer Metaprogrammer
quelle
1
Wird es wirklich in Java 1.7 sein? Es wurde schon lange in C # angefordert, aber ich bezweifle, dass es jemals passieren wird ...
Thomas Levesque
Leider habe ich keine leeren Werte. Diese Java-Syntax sieht allerdings süß aus! Ich werde dies abstimmen, nur weil ich diese Syntax will!
Jon Kragh
3
Thomas: Als ich das letzte Mal tech.puredanger.com/java7 überprüft habe, hat dies impliziert, dass Java es bekommen würde. Aber jetzt, wenn ich es noch einmal überprüfe, heißt es: Null sichere Handhabung: NEIN. Also widerrufe ich meine Aussage und ersetze sie durch eine neue: Sie wurde für Java 1.7 vorgeschlagen, hat es aber nicht geschafft.
Nur ein weiterer Metaprogrammer
Ein zusätzlicher Ansatz ist der von monad.net
Nur ein weiterer Metaprogrammer
1
Scheint wie das ?. Betreiber ist in Visual Studio 2015 https://msdn.microsoft.com/en-us/library/dn986595.aspx
Edward
4

Dieser Code ist "die geringste Menge an Code", aber nicht die beste Vorgehensweise:

try
{
    return ObjectA.PropertyA.PropertyB.PropertyC;
}
catch(NullReferenceException)
{
     return null;
}
Boris Modylevsky
quelle
1
Ich habe Code wie diesen viel gesehen und ohne Rücksicht auf den Leistungsverlust ist das größte Problem, dass es das Debuggen erschwert, weil die eigentliche Ausnahme in Millionen nutzloser Null-Ref-Ausnahmen ertrinkt.
Nur ein weiterer Metaprogrammer
Manchmal ist es amüsant, nach 3 Jahren meine eigene Antwort zu lesen. Ich denke, ich würde heute anders antworten. Ich würde sagen, dass der Code gegen Demeters Gesetz verstößt, und ich würde empfehlen, ihn umzugestalten, damit dies nicht der Fall ist.
Boris Modylevsky
1
Ab heute, 7 Jahre nach der ursprünglichen Antwort, würde ich @Phillip Ngan beitreten und C # 6 mit der folgenden Syntax verwenden: int? value = objectA? .PropertyA? .PropertyB? .PropertyC;
Boris Modylevsky
4

Wenn ich solche Aufrufe verketten muss, verlasse ich mich auf eine von mir erstellte Hilfsmethode, 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);
    }

In Ihrem Fall würden Sie es so verwenden:

    int value = ObjectA
        .TryGet(p => p.PropertyA)
        .TryGet(p => p.PropertyB)
        .TryGet(p => p.PropertyC, defaultVal);
Emanuel
quelle
Ich denke nicht, dass dieser Code funktioniert. Was ist der Typ von defaultVal? var p = neue Person (); Assert.AreEqual (p.TryGet (x => x.FirstName) .TryGet (x => x.LastName) .TryGet (x => x.NickName, "foo"), "foo");
Keith
Das Beispiel, das ich geschrieben habe, sollte als solches gelesen werden: ObjectA.PropertyA.PropertyB.PropertyC. Ihr Code scheint zu versuchen, eine Eigenschaft namens "LastName" von "FirstName" zu laden, was nicht die beabsichtigte Verwendung ist. Ein korrekteres Beispiel wäre etwa: var postcode = person.TryGet (p => p.Address) .TryGet (p => p.Postcode); Übrigens ist meine TryGet () - Hilfsmethode einer neuen Funktion in C # 6.0 sehr ähnlich - dem nullbedingten Operator. Die Verwendung wird folgendermaßen aussehen: var postcode = person? .Address? .Postcode; msdn.microsoft.com/en-us/magazine/dn802602.aspx
Emanuel
3

Ich habe etwas in der neuen C # 6.0 gesehen, dies ist mit '?' anstatt zu überprüfen

zum Beispiel anstatt zu verwenden

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

Sie verwenden einfach

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

Ich hoffe es hat jemandem geholfen.


AKTUALISIEREN:

Das könntest du jetzt so machen

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

Sie könnten dies tun:

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

Oder noch besser könnte dies sein:

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);
Jeffrey L Whitledge
quelle
1

Sie können die folgende Erweiterung verwenden und ich finde es wirklich gut:

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

Und es wird so verwendet:

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

Ich würde Ihre eigene Methode im Typ PropertyA (oder eine Erweiterungsmethode, wenn es nicht Ihr Typ ist) unter Verwendung des ähnlichen Musters wie der Typ Nullable schreiben.

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

   public PropertyBType GetPropertyBOrDefault()
   {
       return PropertyB != null ? PropertyB : defaultValue;
   }
}
Steve Danner
quelle
In diesem Fall kann PropertyB natürlich niemals null sein.
rekursiv
0

Bin gerade über diesen Beitrag gestolpert.

Vor einiger Zeit habe ich in Visual Studio Connect einen Vorschlag zum Hinzufügen eines neuen ???Operators gemacht.

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

Dies würde einige Arbeit vom Framework-Team erfordern, aber nicht die Sprache ändern, sondern nur etwas Compiler-Magie ausführen. Die Idee war, dass der Compiler diesen Code ändern sollte (Syntax nicht erlaubt atm)

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

in diesen Code

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

Bei einer Nullprüfung könnte dies so aussehen

bool isNull = (Order.OrderDetails[0].Product ??? null) == null;
Jürgen Steinblock
quelle
0

Ich habe eine Methode geschrieben, die einen Standardwert akzeptiert. So verwenden Sie ihn:

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

Hier ist der Code:

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);
    }
}
Akira Yamamoto
quelle
1
Diese Lösung wurde bereits in anderen Antworten (wiederholt) bereitgestellt. Es gibt überhaupt keinen Grund, es erneut zu veröffentlichen .
Servy
Ich habe keine gesehen, die einen Standardwert akzeptiert.
Akira Yamamoto
Ich zähle 6 andere, die einen definierten Standardwert verwenden. Anscheinend hast du nicht so genau hingeschaut.
Servy
0

Es ist nicht möglich.
ObjectA.PropertyA.PropertyBschlägt fehl, wenn ObjectAnull aufgrund einer Null-Dereferenzierung vorliegt, was ein Fehler ist.

if(ObjectA != null && ObjectA.PropertyA... funktioniert aufgrund eines Kurzschlusses, dh ObjectA.PropertyAwird nie überprüft, ob dies der Fall ObjectAist null.

Der erste Weg, den Sie vorschlagen, ist der beste und klarste mit Absicht. Wenn überhaupt, könnten Sie versuchen, neu zu gestalten, ohne sich auf so viele Nullen verlassen zu müssen.

DanDan
quelle
-1

Dieser Ansatz ist ziemlich unkompliziert, sobald Sie den Lambda-Gobbly-Gook überwunden haben:

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

Bei Verwendung könnte das so aussehen:

ObjectA objectA = null;

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

Assert.IsNull(objectA.GetPropertyOrDefault(prop => prop.ObjectB));
BlackjacketMack
quelle
Nur neugierig, warum jemand 8 Jahre nach meiner Antwort eine Ablehnung abgegeben hat (was Jahre bevor C # 6s Nullverschmelzung eine Sache war).
BlackjacketMack
-3
var result = nullableproperty ?? defaultvalue;

Der ??(Null-Koaleszenz-Operator) bedeutet, wenn das erste Argument lautet null, geben Sie stattdessen das zweite zurück.

Aridane Álamo
quelle
2
Diese Antwort löst das Problem des OP nicht. Wie würden Sie die Lösung anwenden? Operator zu ObjectA.PropertyA.PropertyB, wenn alle Teile des Ausdrucks (ObjectA, PropertyA und PropertyB) null sein können?
Artemix
Es stimmt, ich glaube, ich habe die Frage überhaupt nicht gelesen. Unmöglich ist nichts, tu es einfach nicht: P static void Main (string [] args) {a ca = new a (); var default_value = new a () {b = neues Objekt ()}; var value = (ca ?? default_value) .b ?? default_value.b; } Klasse a {öffentliches Objekt b = null; }
Aridane Álamo
(ObjectA ?? DefaultMockedAtNull) .PropertyA! = Null? ObjectA.PropertyA.PropertyB: null
Aridane Álamo