J'ai rencontré un problème de compilation aujourd'hui qui m'a déconcerté. Considérez ces deux classes de conteneur.Génériques, héritage et résolution de méthode échouée du compilateur C#
public class BaseContainer<T> : IEnumerable<T>
{
public void DoStuff(T item) { throw new NotImplementedException(); }
public IEnumerator<T> GetEnumerator() { }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { }
}
public class Container<T> : BaseContainer<T>
{
public void DoStuff(IEnumerable<T> collection) { }
public void DoStuff <Tother>(IEnumerable<Tother> collection)
where Tother: T
{
}
}
Les anciens et les DoStuff(T item)
définit celui-ci avec le surcharge DoStuff <Tother>(IEnumerable<Tother>)
spécifiquement pour contourner l'absence de covariance/contravariance de C# (jusqu'à 4 je l'entends).
Ce code
Container<string> c = new Container<string>();
c.DoStuff("Hello World");
frappe une erreur de compilation assez étrange. Notez l'absence de <char>
à partir de l'appel de méthode.
Le type 'char' ne peut pas être utilisé comme paramètre de type 'Tautre' dans le type générique ou la méthode 'Container.DoStuff (System.Collections.Generic.IEnumerable). Il n'y a pas de conversion de boxe de 'char' à 'string'.
Essentiellement, le compilateur essaie de bloquer mon appel à DoStuff(string)
en Container.DoStuff<char>(IEnumerable<char>)
parce que string
met en œuvre IEnumerable<char>
, plutôt que d'utiliser BaseContainer.DoStuff(string)
.
La seule façon que j'ai trouvé pour faire de cette compilation est d'ajouter DoStuff(T)
à la classe dérivée
public class Container<T> : BaseContainer<T>
{
public new void DoStuff(T item) { base.DoStuff(item); }
public void DoStuff(IEnumerable<T> collection) { }
public void DoStuff <Tother>(IEnumerable<Tother> collection)
where Tother: T
{
}
}
Pourquoi le compilateur tente de bloquer une chaîne comme IEnumerable<char>
quand 1), il sait qu'il peut » t (étant donné la présence d'une erreur de compilation) et 2) il a une méthode dans la classe de base qui compile bien? Ai-je mal compris quelque chose sur les génériques ou les trucs de méthode virtuelle en C#? Y a-t-il une autre solution autre que l'ajout d'un new DoStuff(T item)
à Container
?
Je suis d'accord, cela semble bizarre, mais il est correct selon les spécifications. Ceci est une conséquence de l'interaction de deux règles: (1) la vérification de l'applicabilité de la résolution de surcharge survient AVANT la vérification des contraintes, et (2) les méthodes applicables dans les classes dérivées sont TOUJOURS meilleures que les méthodes applicables dans les classes de base. Les deux sont des règles raisonnablement raisonnables; ils arrivent juste à interagir particulièrement mal dans votre cas. –
Pour plus de détails, voir section 7.5.5.1, en particulier les bits qui disent: (1) "Si la meilleure méthode est une méthode générique, les arguments de type (fournis ou inférés) sont vérifiés par rapport aux contraintes ..." et (2) " l'ensemble des méthodes candidates est réduit pour ne contenir que des méthodes provenant des types les plus dérivés ... " –
Finalement, votre problème ici est un problème de conception. Vous surchargez une méthode "DoStuff" pour signifier à la fois "faire des choses à une seule valeur de type T", et "faire des choses à une séquence de valeurs de type T". Cela se heurte à de sérieux problèmes de "résolution d'intention" de plusieurs façons - par exemple, lorsque "type T" est lui-même une séquence. Vous constaterez que les classes de collection existantes dans la BCL ont été soigneusement conçues pour éviter ce problème; les méthodes qui prennent un élément sont appelées "Frob", les méthodes qui prennent une séquence d'éléments sont appelées "FrobRange", par exemple "Add" et "AddRange" dans les listes. –