128 votes

Vérification des nuls profonds, existe-t-il une meilleure méthode ?

Nous sommes tous passés par là, nous avons une propriété profonde comme cake.frosting.berries.loader et nous devons vérifier si elle est nulle pour qu'il n'y ait pas d'exception. La façon de procéder est d'utiliser une instruction if de court-circuitage.

if (cake != null && cake.frosting != null && cake.frosting.berries != null) ...

Il devrait peut-être y avoir un moyen plus simple de vérifier l'ensemble de la chaîne et de voir si elle se heurte à une variable/propriété nulle.

Est-il possible de le faire en utilisant une méthode d'extension, ou serait-ce une fonctionnalité du langage, ou est-ce simplement une mauvaise idée ?

220voto

Eric Lippert Points 300275

Nous avons envisagé d'ajouter une nouvelle opération " ?" au langage qui aurait la sémantique que vous souhaitez. C'est-à-dire que vous diriez

cake?.frosting?.berries?.loader

et le compilateur générera toutes les vérifications de court-circuitage pour vous.

Il n'a pas été retenu pour C# 4, mais peut-être pour une future version hypothétique du langage.

Mise à jour 2014 : Le site ?. L'opérateur est maintenant prévu pour la prochaine version du compilateur Roslyn. Notez qu'il y a toujours un débat sur l'analyse syntaxique et sémantique exacte de l'opérateur.

27voto

driis Points 70872

J'ai été inspiré par cette question pour essayer de trouver comment ce type de vérification profonde des nullités peut être réalisé avec une syntaxe plus facile/plus jolie en utilisant des arbres d'expression. Bien que je sois d'accord avec les réponses indiquant qu'il est possible d'utiliser des arbres d'expression pour la vérification des nullités. pourrait est une mauvaise conception si vous avez souvent besoin d'accéder à des instances situées au plus profond de la hiérarchie, je pense également que dans certains cas, comme la présentation de données, cela peut être très utile.

J'ai donc créé une méthode d'extension, qui vous permettra d'écrire :

var berries = cake.IfNotNull(c => c.Frosting.Berries);

Cela renverra les baies si aucune partie de l'expression n'est nulle. Si null est rencontré, null est retourné. Il y a cependant quelques réserves : dans la version actuelle, elle ne fonctionne qu'avec un accès simple aux membres, et elle ne fonctionne que sur .NET Framework 4, car elle utilise la méthode MemberExpression.Update, qui est nouvelle dans la version 4. Voici le code de la méthode d'extension IfNotNull :

using System;
using System.Collections.Generic;
using System.Linq.Expressions;

namespace dr.IfNotNullOperator.PoC
{
    public static class ObjectExtensions
    {
        public static TResult IfNotNull<TArg,TResult>(this TArg arg, Expression<Func<TArg,TResult>> expression)
        {
            if (expression == null)
                throw new ArgumentNullException("expression");

            if (ReferenceEquals(arg, null))
                return default(TResult);

            var stack = new Stack<MemberExpression>();
            var expr = expression.Body as MemberExpression;
            while(expr != null)
            {
                stack.Push(expr);
                expr = expr.Expression as MemberExpression;
            } 

            if (stack.Count == 0 || !(stack.Peek().Expression is ParameterExpression))
                throw new ApplicationException(String.Format("The expression '{0}' contains unsupported constructs.",
                                                             expression));

            object a = arg;
            while(stack.Count > 0)
            {
                expr = stack.Pop();
                var p = expr.Expression as ParameterExpression;
                if (p == null)
                {
                    p = Expression.Parameter(a.GetType(), "x");
                    expr = expr.Update(p);
                }
                var lambda = Expression.Lambda(expr, p);
                Delegate t = lambda.Compile();                
                a = t.DynamicInvoke(a);
                if (ReferenceEquals(a, null))
                    return default(TResult);
            }

            return (TResult)a;            
        }
    }
}

Il fonctionne en examinant l'arbre d'expression représentant votre expression, et en évaluant les parties l'une après l'autre ; chaque fois, il vérifie que le résultat n'est pas nul.

Je suis sûr que cela pourrait être étendu afin que d'autres expressions que MemberExpression soient supportées. Considérez ceci comme un code de preuve de concept, et s'il vous plaît gardez à l'esprit qu'il y aura une pénalité de performance en l'utilisant (qui n'aura probablement pas d'importance dans de nombreux cas, mais ne l'utilisez pas dans une boucle serrée :-)) )

24voto

John Leidegren Points 21951

J'ai trouvé cette extension très utile pour les scénarios d'imbrication profonde.

public static R Coal<T, R>(this T obj, Func<T, R> f)
    where T : class
{
    return obj != null ? f(obj) : default(R);
}

C'est une idée que j'ai tirée de l'opérateur de coalescence des nuls en C# et T-SQL. L'avantage est que le type de retour est toujours le type de retour de la propriété interne.

De cette façon, tu peux faire ça :

var berries = cake.Coal(x => x.frosting).Coal(x => x.berries);

...ou une légère variation de ce qui précède :

var berries = cake.Coal(x => x.frosting, x => x.berries);

Ce n'est pas la meilleure syntaxe que je connaisse, mais elle fonctionne.

20voto

Mehrdad Afshari Points 204872

Si vous avez besoin d'une telle expression dans votre code, vous enfreignez la directive Loi de Déméter . Vous devriez envisager de revoir la conception afin de ne pas avoir à accéder directement aux propriétés des propriétés des propriétés d'un objet.

Dans les rares cas où il est effectivement judicieux d'enfreindre cette loi, la façon dont elle fonctionne est assez concise, grâce à l'opérateur "et" qui court-circuite.

16voto

Johannes Rudolph Points 19845

En plus de violer la loi de Déméter, comme Mehrdad Afshari l'a déjà souligné, il me semble que vous avez besoin d'un "deep null checking" pour la logique de décision.

C'est le plus souvent le cas lorsque vous souhaitez remplacer des objets vides par des valeurs par défaut. Dans ce cas, vous devriez envisager d'implémenter la méthode Modèle d'objet nul . Il agit comme un substitut d'un objet réel, fournissant des valeurs par défaut et des méthodes de "non-action".

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X