2010-11-25 5 views
13

Pourquoi le composant ci-dessous ne compilera-t-il pas? Quelle est la particularité de l'interface qui pousse le compilateur à penser qu'il ne peut pas passer de Container<T> à T, alors que T est une interface? Je ne pense pas que ce soit une question covariante, car je ne suis pas downcasting, mais c'est peut-être le cas. C'est un peu comme Why C# compiler doesn't call implicit cast operator? mais je ne pense pas que ce soit pareil.La conversion implicite générique C# sur l'interface a échoué

Product pIn =null; 
Product pOut; 
Container<Product> pContainer; 

List<Product> pListIn = null; 
List<Product> pListOut; 
Container<List<Product>> pListContainer; 

IList<Product> pIListIn = null; 
IList<Product> pIListOut; 
Container<IList<Product>> pIListContainer; 

pContainer = pIn; 
pOut = pContainer; // all good 

pListContainer = pListIn; 
pListOut = pListContainer; // all good too 

pIListContainer = pIListIn; // fails , cant do implicit cast for some reason 
pIListOut = pIListContainer; // and here too 

class Container<T> 
{ 
private T value; 

private Container(T item) { value = item; } 

public static implicit operator Container<T>(T item) 
{ 
    return new Container<T>(item); 
} 

public static implicit operator T(Container<T> container) 
{ 
    return container.value; 
} 
} 

Cannot implicitly convert type 'Container<IList<Product>>' to 'IList<Product>'. An explicit conversion exists (are you missing a cast?) 
Cannot implicitly convert type 'IList<Product>' to 'Container<IList<Product>>'. An explicit conversion exists (are you missing a cast?) 
+2

+1 - Intéressant en fait. Curieux de voir la réponse. – Lucero

+1

+1 - J'ai trouvé cette erreur de compilation et je voulais poser cette question! – TDaver

Répondre

13

conversions définies par l'utilisateur ne sont pas autorisés sur les interfaces du tout. Ce serait potentiellement ambigu, parce que le type que vous essayez de convertir pourrait implémenter l'interface elle-même - à quel point ce que la distribution signifie? Une conversion de référence comme une distribution normale ou une invocation de la conversion définie par l'utilisateur?

De la section 10.3.3 du C# 4 spec:

Pour un S et le type cible de type T source donnée, si S ou T sont des types nullable, laissez-S0 et T0 se référer à leurs types fondamentaux, sinon S0 et T0 sont égaux à S et T respectivement. Une classe ou une structure est autorisée à déclarer une conversion d'un type source S vers un type cible T uniquement si toutes les conditions suivantes sont vraies:

  • S0 et T0 sont des types différents.
  • S0 ou T0 est le type de classe ou de structure dans lequel la déclaration d'opérateur a lieu.
  • Ni S0 ni T0 ne sont de type interface.
  • Hors conversions définies par l'utilisateur, une conversion n'existe pas de S à T ou de T à S.

puis plus tard:

Cependant, il est possible de déclarer les opérateurs sur les types génériques qui, pour des arguments de type particuliers, spécifient les conversions qui existent déjà en tant que conversions prédéfinies
...
Dans les cas où une conversion prédéfinie existe entre deux types, toutes les conversions définies par l'utilisateur entre t Les types de tuyaux sont ignorés. Plus précisément:

  • Si une conversion implicite prédéfinie (§ 6.1) existe à partir du type S type T, toutes les conversions définies par l'utilisateur (implicites ou explicites) de S à T sont ignorées.
  • Si une conversion explicite prédéfinie (§6.2) existe du type S au type T, toutes les conversions explicites définies par l'utilisateur de S à T sont ignorées. En outre:
    • Si T est un type d'interface, les conversions implicites définies par l'utilisateur de S à T sont ignorées.
    • Sinon, les conversions implicites définies par l'utilisateur de S à T sont toujours prises en compte.

Notez la première puce imbriquée ici.

(je bien recommande de prendre la main sur les spécifications de la manière. Il est disponible en ligne à various versions and formats, mais le hardcopy annotated edition est aussi une mine d'or de petites pépites de l'équipe et d'autres. Je confesse un certain parti pris ici, comme je suis l'un des annotateurs - mais en ignorant mes trucs, toutes les autres annotations valent la peine d'être lues!)

+0

intéressant intéressant .... votre point sur le type d'implémentation de l'interface elle-même prend tout son sens maintenant que vous l'avez souligné. une autre goutte bien méritée dans vos points océan. J'attends votre nouveau livre en profondeur, alors il faut d'abord passer à travers. ;) – jasper

+0

D'autre part, si S ** n'implante pas ** l'interface T (qui est IMHO le seul mais réel intérêt à faire une conversion définie par l'utilisateur d'un type concret à une interface), y aurait-il quelque chose à gauche empêchant le casting? Ou est-ce juste que l'équipe de compilateur C# avait d'autres poissons à faire frire que de faire cette distinction? – Ssithra

+0

@Ssithra: Imaginez si S ne l'a pas fait, mais un * sous-type * de S l'a fait ... et le type réel au moment de l'exécution était ce sous-type ... que devrait-il se passer? Une conversion définie par l'utilisateur doit-elle être appliquée lorsque ce n'est pas vraiment nécessaire? Mis à part toute autre chose, cela ressemble à une recette pour la confusion. –

Questions connexes