Manières uniques d’utiliser l’opérateur Null Coalescing [fermé]


165

Je sais que la manière standard d'utiliser l'opérateur de fusion Null en C # consiste à définir des valeurs par défaut.

string nobody = null;
string somebody = "Bob Saget";
string anybody = "";

anybody = nobody   ?? "Mr. T"; // returns Mr. T
anybody = somebody ?? "Mr. T"; // returns "Bob Saget"

Mais à quoi d'autre peut- ??on servir? Il ne semble pas aussi utile que l'opérateur ternaire, en plus d'être plus concis et plus facile à lire que:

nobody = null;
anybody = nobody == null ? "Bob Saget" : nobody; // returns Bob Saget

Donc, étant donné que moins de gens connaissent même l'opérateur de fusion nul ...

  • Avez-vous utilisé ??pour autre chose?

  • Est ??nécessaire, ou devriez-vous simplement utiliser l'opérateur ternaire (que la plupart sont familiers avec)

Réponses:


216

Eh bien, tout d'abord, il est beaucoup plus facile à enchaîner que le ternaire standard:

string anybody = parm1 ?? localDefault ?? globalDefault;

contre.

string anyboby = (parm1 != null) ? parm1 
               : ((localDefault != null) ? localDefault 
               : globalDefault);

Cela fonctionne également bien si l'objet null-possible n'est pas une variable:

string anybody = Parameters["Name"] 
              ?? Settings["Name"] 
              ?? GlobalSetting["Name"];

contre.

string anybody = (Parameters["Name"] != null ? Parameters["Name"] 
                 : (Settings["Name"] != null) ? Settings["Name"]
                 :  GlobalSetting["Name"];

12
Le chaînage est un gros plus pour l'opérateur, supprime un tas de FI redondants
chakrit

1
Je l'ai juste utilisé aujourd'hui pour remplacer un simple bloc IF que j'ai écrit avant de connaître l'opérateur ternaire ou nul de coalescence. Les branches true et false de l'instruction IF d'origine ont appelé la même méthode, remplaçant l'un de ses arguments par une valeur différente si une certaine entrée est NULL. Avec l'opérateur de fusion nul, c'est un appel. C'est vraiment puissant lorsque vous avez une méthode qui nécessite deux ou plusieurs substitutions!
David A. Gray

177

Je l'ai utilisé comme une seule ligne à chargement paresseux:

public MyClass LazyProp
{
    get { return lazyField ?? (lazyField = new MyClass()); }
}

Lisible? Décider vous-même.


6
Hmmm, vous avez trouvé un contre-exemple pour "pourquoi quelqu'un voudrait-il l'utiliser comme un IF obscurci" ... qui est en fait très lisible pour moi.
Godeke

6
Quelque chose me manque peut-être (j'utilise principalement Java), mais n'y a-t-il pas une condition de concurrence?
Justin K

9
@Justin K - Il n'y a une condition de concurrence que si plusieurs threads accèdent à la propriété LazyProp du même objet. Il est facilement réparable avec un verrou, si la sécurité des threads de chaque instance est requise. Clairement dans cet exemple, ce n'est pas obligatoire.
Jeffrey L Whitledge le

5
@Jeffrey: Si c'était clair, je n'aurais pas posé la question. :) Quand j'ai vu cet exemple, j'ai immédiatement pensé à un membre singleton, et comme il se trouve que je fais la plupart de mon codage dans un environnement multithread ... Mais, oui, si nous supposons que le code est correct, rien de plus est inutile.
Justin K

7
Il n'est pas nécessaire que ce soit un Singleton pour avoir une condition de course. Juste une instance partagée de la classe qui contient LazyProp et plusieurs threads qui accèdent à LazyProp. Lazy <T> est un meilleur moyen de faire ce genre de chose, et est threadsafe par défaut (vous pouvez choisir de modifier la sécurité des threads de Lazy <T>).
Niall Connaughton

52

Je l'ai trouvé utile de deux manières «légèrement étranges»:

  • Comme alternative pour avoir un outparamètre lors de l'écriture de TryParseroutines (c'est-à-dire retourner la valeur nulle si l'analyse échoue)
  • En tant que représentation «ne sait pas» pour les comparaisons

Ce dernier a besoin d'un peu plus d'informations. Généralement, lorsque vous créez une comparaison avec plusieurs éléments, vous devez voir si la première partie de la comparaison (par exemple l'âge) donne une réponse définitive, puis la partie suivante (par exemple le nom) uniquement si la première partie n'a pas aidé. L'utilisation de l'opérateur de fusion nul signifie que vous pouvez écrire des comparaisons assez simples (que ce soit pour l'ordre ou l'égalité). Par exemple, en utilisant quelques classes d'assistance dans MiscUtil :

public int Compare(Person p1, Person p2)
{
    return PartialComparer.Compare(p1.Age, p2.Age)
        ?? PartialComparer.Compare(p1.Name, p2.Name)
        ?? PartialComparer.Compare(p1.Salary, p2.Salary)
        ?? 0;
}

Certes, j'ai maintenant ProjectionComparer dans MiscUtil, ainsi que quelques extensions, ce qui rend ce genre de chose encore plus facile - mais c'est toujours chouette.

La même chose peut être faite pour vérifier l'égalité des références (ou la nullité) au début de l'implémentation d'Equals.


J'aime ce que vous avez fait avec PartialComparer, mais je recherchais des cas où je devais conserver les variables d'expression évaluées. Je ne suis pas versé dans les lambdas et les extensions, alors pourriez-vous voir si ce qui suit adhère à un modèle similaire (c'est-à-dire que cela fonctionne)? stackoverflow.com/questions/1234263/#1241780
maxwellb

33

Un autre avantage est que l'opérateur ternaire nécessite une double évaluation ou une variable temporaire.

Considérez ceci, par exemple:

string result = MyMethod() ?? "default value";

tandis qu'avec l'opérateur ternaire, il vous reste soit:

string result = (MyMethod () != null ? MyMethod () : "default value");

qui appelle MyMethod deux fois, ou:

string methodResult = MyMethod ();
string result = (methodResult != null ? methodResult : "default value");

Quoi qu'il en soit, l'opérateur de fusion nul est plus propre et, je suppose, plus efficace.


1
+1. C'est l'une des principales raisons pour lesquelles j'aime l'opérateur de fusion nul. C'est particulièrement utile lorsque l'appel MyMethod()a des effets secondaires.
un CVn le

Si MyMethod()n'a aucun effet en dehors du retour d'une valeur, le compilateur sait ne pas l'appeler deux fois, vous n'avez donc vraiment pas à vous soucier de l'efficacité ici dans la majorité des cas.
TinyTimZamboni

Il garde également les choses plus lisibles à mon humble avis quand MyMethod()est une séquence enchaînée d'objets en pointillés. Ex:myObject.getThing().getSecondThing().getThirdThing()
xdhmoore

@TinyTimZamboni, avez-vous une référence pour ce comportement de compilateur?
Kuba Wyrostek

@KubaWyrostek Je n'ai pas de connaissance du fonctionnement spécifique du compilateur C #, mais j'ai une certaine expérience de la théorie du compilateur statique avec llvm. Un compilateur peut adopter un certain nombre d'approches pour optimiser un appel comme celui-ci. Global Value Numbering remarquera que les deux appels à MyMethodsont identiques dans ce contexte, en supposant qu'il MyMethods'agit d'une fonction Pure. Une autre option est la mémorisation automatique ou simplement la fermeture de la fonction dans le cache. D'autre part: en.wikipedia.org/wiki/Global_value_numbering
TinyTimZamboni

23

Une autre chose à considérer est que l'opérateur coalesce n'appelle pas la méthode get d'une propriété deux fois, comme le fait le ternaire.

Il y a donc des scénarios où vous ne devriez pas utiliser ternaire, par exemple:

public class A
{
    var count = 0;
    private int? _prop = null;
    public int? Prop
    {
        get 
        {
            ++count;
            return _prop
        }
        set
        {
            _prop = value;
        }
    }
}

Si tu utilises:

var a = new A();
var b = a.Prop == null ? 0 : a.Prop;

le getter sera appelé deux fois et la countvariable sera égale à 2, et si vous utilisez:

var b = a.Prop ?? 0

la countvariable sera égale à 1, comme il se doit.


4
Cela mérite plus de votes positifs. Je l' ai lu plusieurs fois sooo qui ??est équivalent à ?:.
Kuba Wyrostek

1
Point valide sur un getter appelé deux fois. Mais cet exemple, je considérerais un mauvais modèle de conception pour avoir un tel getter nommé de manière trompeuse pour apporter des modifications à l'objet.
Linas le

15

Le plus grand avantage que je trouve pour l' ??opérateur est que vous pouvez facilement convertir des types valeur nullable en types non nullable:

int? test = null;
var result = test ?? 0; // result is int, not int?

J'utilise fréquemment ceci dans les requêtes Linq:

Dictionary<int, int?> PurchaseQuantities;
// PurchaseQuantities populated via ASP .NET MVC form.
var totalPurchased = PurchaseQuantities.Sum(kvp => kvp.Value ?? 0);
// totalPurchased is int, not int?

Je suis peut-être un peu en retard ici, mais ce deuxième exemple jettera si kvp == null. Et a en Nullable<T>fait une GetValueOrDefaultméthode que j'utilise normalement.
CompuChip

6
KeyValuePair est un type valeur dans le framework .NET, donc l'accès à l'une de ses propriétés ne lèvera jamais une exception de référence nulle. msdn.microsoft.com/en-us/library/5tbh8a42(v=vs.110).aspx
Ryan

9

J'ai utilisé ?? dans mon implémentation de IDataErrorInfo:

public string Error
{
    get
    {
        return this["Name"] ?? this["Address"] ?? this["Phone"];
    }
}

public string this[string columnName]
{
    get { ... }
}

Si une propriété individuelle est dans un état "erreur", j'obtiens cette erreur, sinon j'obtiens null. Fonctionne vraiment bien.


Intéressant. Vous utilisez "ceci" comme propriété. Je n'ai jamais fait ça.
Armstrongest

Ouais, cela fait partie du fonctionnement de IDataErrorInfo. En général, cette syntaxe n'est utile que sur les classes de collection.
Matt Hamilton

4
Vous stockez des messages d'erreur dans this["Name"], this["Address"], etc ??
Andrew

7

Vous pouvez utiliser l'opérateur de fusion null pour le rendre un peu plus propre pour gérer le cas où un paramètre facultatif n'est pas défini:

public void Method(Arg arg = null)
{
    arg = arg ?? Arg.Default;
    ...

Ne serait-ce pas génial si cette ligne pouvait être écrite comme arg ?= Arg.Default?
Jesse de Wit

6

J'aime utiliser l'opérateur de fusion nul pour charger paresseusement certaines propriétés.

Un exemple très simple (et artificiel) juste pour illustrer mon propos:

public class StackOverflow
{
    private IEnumerable<string> _definitions;
    public IEnumerable<string> Definitions
    {
        get
        {
            return _definitions ?? (
                _definitions = new List<string>
                {
                    "definition 1",
                    "definition 2",
                    "definition 3"
                }
            );
        }
    } 
}

Resharper suggérera en fait cela comme un refactor pour une charge paresseuse "traditionnelle".
arichards

5

Est ?? nécessaire, ou devriez-vous simplement utiliser l'opérateur ternaire (que la plupart sont familiers avec)

En fait, mon expérience est que trop peu de gens sont familiers avec l'opérateur ternaire (ou plus correctement, l' opérateur conditionnel ; ?:est «ternaire» dans le même sens qui ||est binaire ou +est soit unaire ou binaire; il se trouve cependant être le seul opérateur ternaire dans de nombreuses langues), donc au moins dans cet échantillon limité, votre instruction échoue là.

De plus, comme mentionné précédemment, il existe une situation majeure où l'opérateur de fusion nul est très utile, et c'est à chaque fois que l'expression à évaluer a des effets secondaires. Dans ce cas, vous ne pouvez pas utiliser l'opérateur conditionnel sans (a) introduire une variable temporaire, ou (b) changer la logique réelle de l'application. (b) n'est clairement pas approprié en aucune circonstance, et bien que ce soit une préférence personnelle, je n'aime pas encombrer la portée de la déclaration avec beaucoup de variables superflues, même si elles sont de courte durée, donc (a) est aussi scénario particulier.

Bien sûr, si vous devez effectuer plusieurs vérifications sur le résultat, l'opérateur conditionnel, ou un ensemble de ifblocs, est probablement l'outil pour le travail. Mais pour le simple "si ceci est nul, utilisez-le, sinon utilisez-le", l'opérateur de fusion nul ??est parfait.


Commentaire très tardif de ma part - mais heureux de voir quelqu'un couvrir qu'un opérateur ternaire est un opérateur avec trois arguments (dont il y en a maintenant plus d'un en C #).
Steve Kidd

5

Une chose que j'ai beaucoup faite ces derniers temps est d'utiliser la fusion nulle pour les sauvegardes as. Par exemple:

object boxed = 4;
int i = (boxed as int?) ?? 99;

Console.WriteLine(i); // Prints 4

C'est également utile pour sauvegarder de longues chaînes ?.qui pourraient échouer

int result = MyObj?.Prop?.Foo?.Val ?? 4;
string other = (MyObj?.Prop?.Foo?.Name as string)?.ToLower() ?? "not there";

4

Le seul problème est que l'opérateur null-coalesce ne détecte pas les chaînes vides.


c'est à dire

string result1 = string.empty ?? "dead code!";

string result2 = null ?? "coalesced!";

PRODUCTION:

result1 = ""

result2 = coalesced!

Je cherche actuellement à remplacer le ?? opérateur pour contourner ce problème. Il serait certainement utile de l'intégrer dans le Framework.

Pensées?


Vous pouvez le faire avec les méthodes d'extension, mais je suis d'accord, ce serait un ajout intéressant au code et très utile dans un contexte Web.
Armstrongest

Oui, c'est un scénario fréquent ... il y a même une méthode spéciale String.IsNullOrEmpty (string) ...
Max Galkin

12
"l'opérateur null-coalesce ne détecte pas les chaînes vides." C'est bien l' opérateur null -coalescing, pas l' opérateur nullOrEmpty -coalescing. Et personnellement, je méprise le mélange de valeurs nulles et vides dans des langages qui distinguent les deux, ce qui rend l'interfaçage avec des choses qui ne sont pas très ennuyeuses. Et je suis un peu obsessionnel-compulsif, donc ça m'ennuie quand les langages / implémentations ne font pas la distinction entre les deux de toute façon, même si je comprends le raisonnement pourquoi (comme dans [la plupart des implémentations de?] SQL).
JAB

3
??ne peut pas être surchargé: msdn.microsoft.com/en-us/library/8edha89s(v=vs.100).aspx - ce serait bien d'avoir une surcharge. J'utilise une combinaison: s1.Nullify() ?? s2.Nullify()string Nullify(this s)renvoie un nulldans les cas où la chaîne est vide.
Kit du

Le seul problème? Je me suis juste retrouvé à vouloir ?? = et j'ai trouvé ce fil en voyant s'il y avait un moyen de le faire. (Situation: la première passe a chargé les cas d'exception, maintenant je veux revenir en arrière et charger les valeurs par défaut dans tout ce qui n'a pas déjà été chargé.)
Loren Pechtel

3

Est ?? nécessaire, ou devriez-vous simplement utiliser l'opérateur ternaire (que la plupart sont familiers avec)

Vous devez utiliser ce qui exprime le mieux votre intention. Comme il est un opérateur coalesce null, l' utiliser .

D'un autre côté, comme il est si spécialisé, je ne pense pas qu'il ait d'autres utilisations. J'aurais préféré une surcharge appropriée de l' ||opérateur, comme le font d'autres langages. Ce serait plus parcimonieux dans la conception du langage. Enfin bon …


3

Cool! Comptez-moi comme quelqu'un qui ne connaissait pas l'opérateur de fusion nul - c'est un truc assez sympa.

Je trouve cela beaucoup plus facile à lire que l'opérateur ternaire.

Le premier endroit qui me vient à l'esprit où je pourrais l'utiliser est de conserver tous mes paramètres par défaut au même endroit.

public void someMethod( object parm2, ArrayList parm3 )
{ 
  someMethod( null, parm2, parm3 );
}
public void someMethod( string parm1, ArrayList parm3 )
{
  someMethod( parm1, null, parm3 );
}
public void someMethod( string parm1, object parm2, )
{
  someMethod( parm1, parm2, null );
}
public void someMethod( string parm1 )
{
  someMethod( parm1, null, null );
}
public void someMethod( object parm2 )
{
  someMethod( null, parm2, null );
}
public void someMethod( ArrayList parm3 )
{
  someMethod( null, null, parm3 );
}
public void someMethod( string parm1, object parm2, ArrayList parm3 )
{
  // Set your default parameters here rather than scattered through the above function overloads
  parm1 = parm1 ?? "Default User Name";
  parm2 = parm2 ?? GetCurrentUserObj();
  parm3 = parm3 ?? DefaultCustomerList;

  // Do the rest of the stuff here
}

2

Un peu de cas d'utilisation étrange, mais j'avais une méthode où un IDisposableobjet est potentiellement passé en tant qu'arg (et donc supprimé par le parent), mais pourrait également être nul (et devrait donc être créé et supprimé dans la méthode locale)

Pour l'utiliser, le code ressemblait soit à

Channel channel;
Authentication authentication;

if (entities == null)
{
    using (entities = Entities.GetEntities())
    {
        channel = entities.GetChannelById(googleShoppingChannelCredential.ChannelId);
        [...]
    }
}
else
{
    channel = entities.GetChannelById(googleShoppingChannelCredential.ChannelId);
    [...]
}

Mais avec une fusion nulle devient beaucoup plus nette

using (entities ?? Entities.GetEntities())
{
    channel = entities.GetChannelById(googleShoppingChannelCredential.ChannelId);
    [...]
}

0

J'ai utilisé comme ceci:

for (int i = 0; i < result.Count; i++)
            {
                object[] atom = result[i];

                atom[3] = atom[3] ?? 0;
                atom[4] = atom[4] != null ? "Test" : string.Empty;
                atom[5] = atom[5] ?? "";
                atom[6] = atom[6] ?? "";
                atom[7] = atom[7] ?? "";
                atom[8] = atom[8] ?? "";
                atom[9] = atom[9] ?? "";
                atom[10] = atom[10] ?? "";
                atom[12] = atom[12] ?? false; 
            }
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.