web-dev-qa-db-fra.com

pourquoi préférons-nous? à ?? opérateur en c #?

J'ai récemment trouvé que nous pouvons utiliser ?? opérateur pour vérifier les valeurs nulles. Veuillez vérifier les exemples de code ci-dessous:

   var res = data ?? new data();

C'est exactement similaire à

   var res = (data==null) ? new data() : data ;

J'ai vérifié l'ensemble du référentiel source de mon projet et certains autres projets open source. Et ça ?? opérateur n'a jamais été utilisé.

Je me demande simplement s'il y a une raison derrière cela, comme des problèmes de performances ou quelque chose du genre?

MODIFIER:

Je viens de mettre à jour mon exemple de code en fonction des commentaires de recursive & Anton. C'est une erreur de négligence. :(

39
RameshVel

L'opérateur de fusion null est beaucoup plus clair lors de la vérification de null, c'est son objectif principal. Il peut également être enchaîné.

object a = null;
object b = null;
object c = new object();
object d = a ?? b ?? c; //d == c.

Alors que cet opérateur est limité à la vérification de null, l'opérateur ternaire ne l'est pas. Par exemple

bool isQuestion = true;
string question = isQuestion ? "Yes" : "No";

Je pense que les gens ne sont tout simplement pas conscients de l'opérateur de fusion nul, ils utilisent donc l'opérateur ternaire à la place. Le ternaire existait avant C # dans la plupart des langages de style C donc si vous ne connaissez pas C # à l'intérieur et à l'extérieur et/ou que vous avez programmé dans un autre langage, le ternaire est un choix naturel. Si vous vérifiez la valeur null, utilisez l'opérateur de fusion null, il est conçu pour cela et l'IL est légèrement optimisé (comparez ?? à un if then else).

Voici un exemple comparant l'utilisation de chacun

object a = null;
object b = null;
object c = null;

object nullCoalesce = a ?? b ?? c;

object ternary = a != null ? a : b != null ? b : c;

object ifThenElse;

if (a != null)
    ifThenElse = a;
else if (b != null)
    ifThenElse = b;
else if (c != null)
    ifThenElse = c;

Tout d'abord, regardez simplement la syntaxe de la fusion nulle, c'est beaucoup plus clair. Ternary est vraiment déroutant. Regardons maintenant l'IL

Null Coalesce uniquement

.entrypoint
.maxstack 2
.locals init (
    [0] object a,
    [1] object b,
    [2] object c,
    [3] object nullCoalesce)
L_0000: ldnull 
L_0001: stloc.0 
L_0002: ldnull 
L_0003: stloc.1 
L_0004: newobj instance void [mscorlib]System.Object::.ctor()
L_0009: stloc.2 
L_000a: ldloc.0 
L_000b: dup 
L_000c: brtrue.s L_0015
L_000e: pop 
L_000f: ldloc.1 
L_0010: dup 
L_0011: brtrue.s L_0015
L_0013: pop 
L_0014: ldloc.2 
L_0015: stloc.3 
L_0016: ldloc.3 
L_0017: call void [mscorlib]System.Console::WriteLine(object)
L_001c: ret 

Ternaire uniquement

.entrypoint
.maxstack 2
.locals init (
    [0] object a,
    [1] object b,
    [2] object c,
    [3] object ternary)
L_0000: ldnull 
L_0001: stloc.0 
L_0002: ldnull 
L_0003: stloc.1 
L_0004: newobj instance void [mscorlib]System.Object::.ctor()
L_0009: stloc.2 
L_000a: ldloc.0 
L_000b: brtrue.s L_0016
L_000d: ldloc.1 
L_000e: brtrue.s L_0013
L_0010: ldloc.2 
L_0011: br.s L_0017
L_0013: ldloc.1 
L_0014: br.s L_0017
L_0016: ldloc.0 
L_0017: stloc.3 
L_0018: ldloc.3 
L_0019: call void [mscorlib]System.Console::WriteLine(object)
L_001e: ret 

Si alors sinon seulement

.entrypoint
.maxstack 1
.locals init (
    [0] object a,
    [1] object b,
    [2] object c,
    [3] object ifThenElse)
L_0000: ldnull 
L_0001: stloc.0 
L_0002: ldnull 
L_0003: stloc.1 
L_0004: newobj instance void [mscorlib]System.Object::.ctor()
L_0009: stloc.2 
L_000a: ldloc.0 
L_000b: brfalse.s L_0011
L_000d: ldloc.0 
L_000e: stloc.3 
L_000f: br.s L_001a
L_0011: ldloc.1 
L_0012: brfalse.s L_0018
L_0014: ldloc.1 
L_0015: stloc.3 
L_0016: br.s L_001a
L_0018: ldloc.2 
L_0019: stloc.3 
L_001a: ldloc.3 
L_001b: call void [mscorlib]System.Console::WriteLine(object)
L_0020: ret 

IL n'est pas l'un de mes points forts, alors peut-être que quelqu'un peut modifier ma réponse et la développer. J'allais expliquer ma théorie, mais je préfère ne pas me confondre avec les autres. Le nombre de LOC est similaire pour les trois, mais tous les opérateurs IL ne prennent pas le même temps pour s'exécuter.

58
Bob

Le ?? (également connu sous le nom de opérateur de fusion null ) est moins connu que l'opérateur ternaire, car il a fait ses débuts avec .NET 2.0 et les types Nullable. Les raisons de ne pas l'utiliser incluent probablement ne pas commencer à savoir qu'il existe, ou être plus familier avec l'opérateur ternaire.

Cela dit, la vérification de null n'est pas la seule chose pour laquelle l'opérateur ternaire est bon, donc ce n'est pas un remplacement pour lui en tant que tel, mais plutôt une meilleure alternative pour un besoin très spécifique. :)

12
Rytmis

Une raison à laquelle je peux penser est que cet opérateur a été introduit dans .NET 2.0, de sorte que le code pour .NET 1.1 ne peut pas l'avoir.

Je suis d'accord avec vous, nous devrions l'utiliser plus souvent.

réf lien

6
Binoj Antony

Basé sur Bob's answer

public object nullCoalesce(object a, object b, object c)
{
    return a ?? b ?? c;
}
public object ternary(object a, object b, object c)
{
    return a != null ? a : b != null ? b : c;
}
public object ifThenElse(object a, object b, object c)
{
    if (a != null)
        return a;
    else if (b != null)
        return b;
    else
        return c;
}

... c'est l'IL des versions de version ...

.method public hidebysig instance object nullCoalesce(
    object a, 
    object b, 
    object c) cil managed
{
    .maxstack 8
    L_0000: ldarg.1 
    L_0001: dup 
    L_0002: brtrue.s L_000b
    L_0004: pop 
    L_0005: ldarg.2 
    L_0006: dup 
    L_0007: brtrue.s L_000b
    L_0009: pop 
    L_000a: ldarg.3 
    L_000b: ret 
}

.method public hidebysig instance object ternary(
    object a, 
    object b, 
    object c) cil managed
{
    .maxstack 8
    L_0000: ldarg.1 
    L_0001: brtrue.s L_000a
    L_0003: ldarg.2 
    L_0004: brtrue.s L_0008
    L_0006: ldarg.3 
    L_0007: ret 
    L_0008: ldarg.2 
    L_0009: ret 
    L_000a: ldarg.1 
    L_000b: ret 
}

.method public hidebysig instance object ifThenElse(
    object a, 
    object b, 
    object c) cil managed
{
    .maxstack 8
    L_0000: ldarg.1 
    L_0001: brfalse.s L_0005
    L_0003: ldarg.1 
    L_0004: ret 
    L_0005: ldarg.2 
    L_0006: brfalse.s L_000a
    L_0008: ldarg.2 
    L_0009: ret 
    L_000a: ldarg.3 
    L_000b: ret 
}
4
Matthew Whited

Une des raisons (comme d'autres l'ont déjà touché) est probablement le manque de sensibilisation. Cela pourrait aussi être (comme dans mon cas), un souhait de réduire autant que possible le nombre d'approches pour faire des choses similaires dans une base de code. J'ai donc tendance à utiliser l'opérateur ternaire pour toutes les situations compactes si-une-condition-est-remplie-faire-ceci-autrement-faire-cela.

Par exemple, je trouve les deux déclarations suivantes assez similaires sur le plan conceptuel:

return a == null ? string.Empty : a;    
return a > 0 ? a : 0;
2
Fredrik Mörk

Je pense que c'est juste une habitude d'autres langues. AUTANT QUE JE SACHE, ?? L'opérateur n'est utilisé dans aucune autre langue.

1
Karel Bílek

J'aurais pensé l'équivalent de

var res = data ?? data.toString();

serait

var res = (data!=null) ? data : data.toString();
0
Bryan