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.