Le type de l’expression conditionnelle ne peut pas être déterminé?

Je viens de rencontrer ceci (code composé pour démontrer le “problème”):

public ICollection CreateCollection(int x) { ICollection collection = x == 0 ? new List() : new LinkedList(); return collection; } 

Le compilateur se plaint:

Fehler CS0173: Les types de fichiers sont identiques à ceux qui sont implémentés. Cliquez ici pour afficher “Système.Collections.Generic.List” et “System.Collections.Generic.LinkedList”.

Ce qui se traduit approximativement par:

Le type de l’opérateur conditionnel ne peut pas être déterminé car il n’y a pas de conversion implicite entre List et LinkedList.

Je peux voir pourquoi le compilateur se plaint, mais bon, allez. Il essaie de jouer stupide. Je peux voir que les deux expressions ne sont pas du même type mais ont un ancêtre commun et, en prime, le type du côté gauche est également un ancêtre commun. Je suis sûr que le compilateur peut le voir aussi. Je pourrais comprendre l’erreur si le côté gauche était déclaré comme var .

Qu’est-ce que j’oublie ici?

Modifier:

J’accepte les explications de James Gaunt. Peut-être juste pour que ce soit clair. Je peux très bien lire les spécifications du compilateur. Je voulais comprendre pourquoi. Pourquoi quelqu’un a-t-il pris la décision d’écrire les spécifications de cette façon? Il doit y avoir une raison derrière cette conception. Selon James, le principe de conception est «pas de sursockets». CodeInChaos explique également les sursockets que vous pourriez rencontrer si le compilateur essayait de déduire le type d’ancêtres communs.

L’expression (a? B: c) doit être résolue en un type. Le type sera soit le type de b ou c. Si ceux-ci sont différents (et qu’il n’y a pas de conversion implicite de l’un à l’autre), le compilateur ne sait pas de quel type il s’agit au moment de la compilation.

Vous pouvez dire que cela devrait en déduire qu’il existe un type de racine commun, mais qu’il existe toujours un type de racine commun (par exemple, Object).

En général, le compilateur C # n’essayera pas de deviner ce que vous voulez dire. Si vous souhaitez utiliser le type de racine commune, transformez b et c en ce type.

Ce type de logique est omniprésent dans la conception de C #, il est parfois un peu agaçant, mais bien plus souvent, il vous empêche de commettre des erreurs.

En raison des interfaces, ils peuvent avoir plusieurs ancêtres communs différents.

On pourrait append une exigence selon laquelle il ne convertit automatiquement que si l’ancêtre est sans ambiguïté. Mais l’ajout d’interfaces supplémentaires implémentées par une classe devient alors un changement radical. Et cela pourrait ne pas être souhaitable.

Par exemple, supposons que ces types ISerializeable implémentés par ISerializeable . Cela ne devrait pas changer le comportement de votre code, mais si vous preniez en charge cette conversion sur une interface commune, ce serait le cas.

edit: Pensé un peu plus à ce sujet et remarqué que cette fonction a déjà exactement le même problème:

 T MyFunc(T left,T right) 

Et ce code ne comstack pas:

 ICollection r=MyFunc(new List() , new LinkedList()); 

car il ne peut pas décider quel type utiliser comme type-paramètre T Le comportement de l’opérateur?: Est donc cohérent avec la résolution de surcharge.

Le côté gauche n’est pas du tout pris en compte lors de la détermination du type du côté droit.

Ce n’est que lorsque le compilateur a déterminé indépendamment le type du membre de droite qu’il vérifie la compatibilité des affectations avec le membre de gauche.

En ce qui concerne votre affirmation selon laquelle les deux types “ont un ancêtre commun” : s’agirait-il de ICollection , IEnumerable , ICollection , IEnumerable ou Object ? Quelle heuristique le compilateur doit-il utiliser pour déterminer sans ambiguïté le type souhaité? Le compilateur vous demande simplement de spécifier, plutôt que d’essayer de deviner votre intention.

C’est simplement la définition de ?: Exiger des types égaux.
Vous pouvez bien sûr utiliser

 ? (ICollection) new List() : (ICollection) new LinkedList(); 

Ou utilisez simplement un if/else .

Selon la référence C #, § 14.13,

[Donné] Une expression conditionnelle de la forme b ? x : y b ? x : y

  • Si X et Y sont du même type, il s’agit du type de l’expression conditionnelle.
  • Sinon, si une conversion implicite (§13.1) existe de X à Y, mais pas de Y à X, alors Y est le type de l’expression conditionnelle.
  • Sinon, si une conversion implicite (§13.1) existe de Y à X, mais pas de X à Y, X est le type de l’expression conditionnelle.
  • Sinon, aucun type d’expression ne peut être déterminé et une erreur de compilation se produit.

Dans votre cas, X et Y ont tous deux une conversion en Z, mais cela n’aide en rien. C’est un principe général dans le langage que le compilateur ne regarde même pas la variable cible lors de l’application des règles. Exemple simple: double a = 7 / 2; // a becomes 3.0 double a = 7 / 2; // a becomes 3.0

Donc, après avoir lu ceci, il suffirait de ne convertir qu’un des résultats en ICollection . Je n’ai pas testé ça.