web-dev-qa-db-fra.com

C # manière élégante de vérifier si la propriété d'une propriété est nulle

En C #, dites que vous voulez extraire une valeur de PropertyC dans cet exemple et que ObjectA, PropertyA et PropertyB peuvent tous être null.

ObjectA.PropertyA.PropertyB.PropertyC

Comment obtenir PropertyC en toute sécurité avec le moins de code possible?

En ce moment je vérifierais:

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

Ce serait bien de faire quelque chose de plus semblable à ceci (pseudo-code).

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

Peut-être même plus loin s'est effondré avec un opérateur nul coalescent.

EDIT À l'origine, je disais que mon deuxième exemple ressemblait à js, mais je le changeai en code psuedo, car il était correctement indiqué que cela ne fonctionnerait pas avec js.

84
Jon Kragh

En C # 6, vous pouvez utiliser le Opérateur Null Conditionnel . Donc, le test original sera:

int? value = objectA?.PropertyA?.PropertyB?.PropertyC;
92
Phillip Ngan

Méthode d'extension courte:

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

En utilisant

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

Cette méthode d'extension simple et bien plus encore, vous pouvez trouver sur http://devtalk.net/csharp/chained-null-checks-and-the-maybe-monad/

MODIFIER:

Après l'avoir utilisé pendant un moment, je pense que le nom correct pour cette méthode devrait être IfNotNull () au lieu de l'original With ().

26

Pouvez-vous ajouter une méthode à votre classe? Si non, avez-vous pensé à utiliser des méthodes d'extension? Vous pouvez créer une méthode d'extension pour votre type d'objet appelée GetPropC().

Exemple:

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

Usage:

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

Soit dit en passant, cela suppose que vous utilisiez .NET 3 ou une version ultérieure.

16
Sam

La façon dont vous le faites est correcte.

Vous pourriez utiliser une astuce semblable à celle décrite ici , en utilisant les expressions Linq:

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

Mais il est beaucoup plus lent que de vérifier manuellement chaque propriété ...

12
Thomas Levesque

Refactor pour observer le loi de Demeter

11
rtalbot

Mise à jour 2014: C # 6 a un nouvel opérateur ?. divers appelés 'navigation sécurisée' ou 'propagation par zéro'

parent?.child

Lire http://blogs.msdn.com/b/jerrynixon/archive/2014/02/26/at-last-c-is-getting-sometimes-called-the-safe- navigation-operator.aspx pour plus de détails

Cela a longtemps été une demande très populaire https://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/3990187-add-operator-to-c-?tracking_code= 594c10a522f8e9bc987ee4a5e2c0b38d

10
Colonel Panic

Vous recherchez évidemment le Monad Nullable:

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

devient

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

Ceci retourne null, si l’une des propriétés nullable est null; sinon, la valeur de Value.

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

Méthodes d'extension 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);
    }
}
9
dtb

Ce code est "la moindre quantité de code", mais pas la meilleure pratique:

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

En supposant que vous ayez des valeurs vides de types, une approche serait la suivante:

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

Je suis un grand fan de C # mais une très jolie chose dans le nouveau Java (1.7?) Est l'opérateur.?

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

J'ai vu quelque chose dans le nouveau C # 6.0, c'est en utilisant '?' au lieu de vérifier

par exemple au lieu d'utiliser

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

vous utilisez simplement

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

J'espère que ça a aidé quelqu'un.


MISE À JOUR:

Tu pourrais faire comme ça maintenant

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

Quand j'ai besoin d'enchaîner des appels comme ça, je m'appuie sur une méthode d'assistance que j'ai créée, 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);
    }

Dans votre cas, vous l'utiliseriez comme suit:

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

Vérifiez cet article de blog out. Je pense que c'est une méthode très élégante pour les contrôles nuls chaînés. Il y a beaucoup d'implémentations similaires, mais j'aime bien celle-ci car elle arrête l'évaluation dès qu'une valeur nulle est trouvée dans la chaîne.

Tout le code source est sur github .

4
codeConcussion

Vous pourriez faire ceci:

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

Ou mieux encore pourrait être ceci:

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
Jeffrey L Whitledge

Ce n'est pas possible. ObjectA.PropertyA.PropertyB échouera si ObjectA est null en raison d'un déréférencement nul, qui est une erreur.

if (ObjectA! = null && ObjectA.PropertyA ... fonctionne en raison d'un court-circuit, c'est-à-dire que ObjectA.PropertyA ne sera jamais vérifié si ObjectA est null.

La première façon que vous proposez est la meilleure et la plus claire avec l'intention. Si vous pouviez faire quelque chose, essayez de modifier la conception sans avoir à compter sur autant de valeurs nulles.

1
DanDan

Propagation nulle prévue dans C # vNext, générée par Roslyn


https://roslyn.codeplex.com/discussions/54088

1
Adam Houldsworth

vous pouvez utiliser l'extension suivante et je pense que c'est vraiment bien:

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

Et c'est utilisé comme ça:

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

Je suis tombé par hasard sur ce post.

Il y a quelque temps, j'ai suggéré à Visual Studio Connect d'ajouter un nouveau ??? opérateur.

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

Cela demanderait un peu de travail de la part de l’équipe du framework, mais il n’aurait pas besoin de modifier le langage mais juste de faire un peu de magie pour le compilateur. L'idée était que le compilateur devrait changer ce code (syntaxe non autorisée atm)

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

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

Pour un contrôle nul cela pourrait ressembler à

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

J'écrirais votre propre méthode dans le type de PropertyA (ou une méthode d'extension si ce n'est pas votre type) en utilisant le modèle similaire au type Nullable.

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

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

Cette approche est assez simple une fois que vous avez dépassé le 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);
        }
    }

Avec une utilisation qui pourrait ressembler à:

ObjectA objectA = null;

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

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

J'ai écrit une méthode qui accepte une valeur par défaut, voici comment l'utiliser:

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

Voici le 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);
    }
}
0
Akira Yamamoto