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 string
parce 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 string
n'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.