Comment null + true est-il une chaîne?

112

Puisque truen'est pas un type de chaîne, comment est null + trueune chaîne?

string s = true;  //Cannot implicitly convert type 'bool' to 'string'   
bool b = null + true; //Cannot implicitly convert type 'string' to 'bool'

Quelle est la raison derrière cela?

Javed Akram
la source
27
Vous faites quelque chose qui n'a pas de sens et ensuite n'aimez pas le message généré par le compilateur? Il s'agit d'un code C # invalide ... que vouliez-vous exactement qu'il fasse?
Hogan
19
@Hogan: Le code semble assez aléatoire et académique pour que la question soit posée par pure curiosité. Juste ma supposition ...
BoltClock
8
null + true renvoie 1 en JavaScript.
Fatih Acet du

Réponses:

147

Aussi bizarre que cela puisse paraître, il s'agit simplement de suivre les règles de la spécification du langage C #.

De la section 7.3.4:

Une opération de la forme x op y, où op est un opérateur binaire surchargeable, x est une expression de type X et y est une expression de type Y, est traitée comme suit:

  • L'ensemble des opérateurs candidats définis par l'utilisateur fournis par X et Y pour l'opérateur d'opération op (x, y) est déterminé. L'ensemble est constitué de l'union des opérateurs candidats fournis par X et des opérateurs candidats fournis par Y, chacun déterminé selon les règles du §7.3.5. Si X et Y sont du même type, ou si X et Y sont dérivés d'un type de base commun, alors les opérateurs candidats partagés n'apparaissent qu'une seule fois dans l'ensemble combiné.
  • Si l'ensemble des opérateurs candidats définis par l'utilisateur n'est pas vide, il devient l'ensemble des opérateurs candidats pour l'opération. Sinon, les implémentations d'op d'opérateur binaire prédéfinies, y compris leurs formes levées, deviennent l'ensemble des opérateurs candidats pour l'opération. Les implémentations prédéfinies d'un opérateur donné sont spécifiées dans la description de l'opérateur (§7.8 à §7.12).
  • Les règles de résolution de surcharge du §7.5.3 sont appliquées à l'ensemble des opérateurs candidats pour sélectionner le meilleur opérateur par rapport à la liste d'arguments (x, y), et cet opérateur devient le résultat du processus de résolution de surcharge. Si la résolution de surcharge ne parvient pas à sélectionner un seul meilleur opérateur, une erreur de liaison se produit.

Alors, parcourons ceci à tour de rôle.

X est le type nul ici - ou pas du tout un type, si vous voulez y penser de cette façon. Il ne fournit aucun candidat. Y est bool, qui ne fournit aucun +opérateur défini par l'utilisateur . Ainsi, la première étape ne trouve aucun opérateur défini par l'utilisateur.

Le compilateur passe ensuite au deuxième point, en regardant à travers l'opérateur binaire prédéfini + implémentations et leurs formes levées. Ceux-ci sont énumérés dans la section 7.8.4 de la spécification.

Si vous regardez à travers ces opérateurs prédéfinis, le seul qui soit applicable est string operator +(string x, object y). Ainsi, l'ensemble candidat a une seule entrée. Cela rend le dernier point très simple ... la résolution de surcharge sélectionne cet opérateur, donnant un type d'expression global de string.

Un point intéressant est que cela se produira même s'il existe d'autres opérateurs définis par l'utilisateur disponibles sur des types non mentionnés. Par exemple:

// Foo defined Foo operator+(Foo foo, bool b)
Foo f = null;
Foo g = f + true;

C'est bien, mais il n'est pas utilisé pour un littéral nul, car le compilateur ne sait pas chercher Foo. Il ne sait à prendre en compte que stringparce qu'il s'agit d'un opérateur prédéfini explicitement répertorié dans la spécification. (En fait, ce n'est pas un opérateur défini par le type de chaîne ... 1 ) Cela signifie que la compilation échouera:

// Error: Cannot implicitly convert type 'string' to 'Foo'
Foo f = null + true;

D'autres types de deuxième opérande utiliseront bien sûr d'autres opérateurs:

var x = null + 0; // x is Nullable<int>
var y = null + 0L; // y is Nullable<long>
var z = null + DayOfWeek.Sunday; // z is Nullable<DayOfWeek>

1 Vous vous demandez peut-être pourquoi il n'y a pas d'opérateur string +. C'est une question raisonnable, et je ne fais que deviner la réponse, mais considérez cette expression:

string x = a + b + c + d;

S'il stringn'y avait pas de casse spéciale dans le compilateur C #, cela finirait aussi efficacement:

string tmp0 = (a + b);
string tmp1 = tmp0 + c;
string x = tmp1 + d;

Cela a donc créé deux chaînes intermédiaires inutiles. Cependant, comme il existe un support spécial dans le compilateur, il est en fait capable de compiler ce qui précède comme:

string x = string.Concat(a, b, c, d);

qui peut créer une seule chaîne exactement de la bonne longueur, en copiant toutes les données une seule fois. Agréable.

Jon Skeet
la source
'donnant un type d'expression global de chaîne' Je ne suis tout simplement pas d'accord. L'erreur vient de truene pas être convertible en string. Si l'expression était valide, le type serait string, mais dans ce cas, l'échec de la conversion en chaîne fait de l'expression entière une erreur et n'a donc pas de type.
leppie
6
@leppie: l'expression est valide et son type est une chaîne. Essayez "var x = null + true;" - il compile et xest de type string. Notez que la signature utilisée ici est string operator+(string, object)- elle est convertie boolen object(ce qui est bien), pas en string.
Jon Skeet
Merci Jon, comprenez maintenant. BTW section 14.7.4 dans la version 2 de la spécification.
leppie
@leppie: Est-ce que c'est dans la numérotation ECMA? Cela a toujours été très différent :(
Jon Skeet
47
Dans 20 Minutes. Il a écrit tout cela en 20 minutes. Il devrait écrire un livre ou autre chose ... oh attendez.
Epaga
44

La raison en est qu'une fois que vous avez introduit le, +les règles de liaison de l'opérateur C # entrent en jeu. Il considérera l'ensemble des +opérateurs disponibles et sélectionnera la meilleure surcharge. L'un de ces opérateurs est le suivant

string operator +(string x, object y)

Cette surcharge est compatible avec les types d'argument de l'expression null + true. Par conséquent, il est sélectionné comme opérateur et est évalué comme essentiellement ((string)null) + truece qui correspond à la valeur "True".

La section 7.7.4 de la spécification du langage C # contient les détails autour de cette résolution.

JaredPar
la source
1
Je remarque que l'IL généré passe directement à l'appel de Concat. Je pensais voir un appel à la fonction opérateur + là-dedans. Serait-ce simplement une optimisation en ligne?
quentin-stardans
1
@qstarin Je crois que la raison est qu'il n'y a pas vraiment un operator+pour string. Au lieu de cela, il n'existe que dans l'esprit du compilateur et il le traduit simplement en appels àstring.Concat
JaredPar
1
@qstarin @JaredPar: J'y suis allé un peu plus dans ma réponse.
Jon Skeet
11

Le compilateur part à la recherche d'un opérateur + () qui peut d'abord prendre un argument nul. Aucun des types de valeur standard n'est qualifié, null n'est pas une valeur valide pour eux. La seule et unique correspondance est System.String.operator + (), il n'y a pas d'ambiguïté.

Le 2ème argument de cet opérateur est également une chaîne. Cela va kapooey, ne peut pas convertir implicitement bool en chaîne.

Hans Passant
la source
10

Fait intéressant, en utilisant Reflector pour inspecter ce qui est généré, le code suivant:

string b = null + true;
Console.WriteLine(b);

est transformé en ceci par le compilateur:

Console.WriteLine(true);

Le raisonnement derrière cette "optimisation" est un peu bizarre je dois dire, et ne rime pas avec la sélection d'opérateurs que j'attendrais.

En outre, le code suivant:

var b = null + true; 
var sb = new StringBuilder(b);

se transforme en

string b = true; 
StringBuilder sb = new StringBuilder(b);

string b = true;n'est en fait pas accepté par le compilateur.

Peter Lillevold
la source
8

nullsera converti en chaîne nulle, et il y a un convertisseur implicite de bool en chaîne donc le truesera converti en chaîne et ensuite, l' +opérateur sera appliqué: c'est comme: string str = "" + true.ToString ();

si vous le vérifiez avec Ildasm:

string str = null + true;

c'est comme ci-dessous:

.locals init ([0] string str)
  IL_0000:  nop
  IL_0001:  ldc.i4.1
  IL_0002:  box        [mscorlib]System.Boolean
  IL_0007:  call       string [mscorlib]System.String::Concat(object)
  IL_000c:  stloc.0
Saeed Amiri
la source
5
var b = (null + DateTime.Now); // String
var b = (null + 1);            // System.Nullable<Int32> | same with System.Single, System.Double, System.Decimal, System.TimeSpan etc
var b = (null + new Object()); // String | same with any ref type

Fou?? Non, il doit y avoir une raison derrière cela.

Quelqu'un appelle Eric Lippert...

décyclone
la source
5

La raison en est la commodité (la concaténation de chaînes est une tâche courante).

Comme BoltClock l'a dit, l'opérateur '+' est défini sur les types numériques, les chaînes et peut également être défini pour nos propres types (surcharge d'opérateurs).

S'il n'y a pas d'opérateur '+' surchargé sur les types de l'argument et qu'il ne s'agit pas de types numériques, le compilateur utilise par défaut la concaténation de chaînes.

Le compilateur insère un appel à String.Concat(...)lorsque vous concaténez en utilisant '+', et l'implémentation de Concat appelle ToString sur chaque objet qui lui est passé.

quentin-starin
la source