Je crois que le compilateur ne la meilleure chose à VB.NET avec l'avertissement, mais je ne pense toujours pas que c'est aller assez loin. Malheureusement, la "bonne chose" probablement besoin interdisant quelque chose qui est potentiellement utiles(mise en place de la même interface avec deux covariant et contravariant paramètres de type générique) ou de l'introduction de quelque chose de nouveau à la langue.
Comme il est, il n'y a pas de place le compilateur pourrait affecter une erreur que l' HungryWolf
classe. C'est le moment où une classe est prétendant à savoir comment faire quelque chose qui pourrait potentiellement être ambiguë. C'est en indiquant
Je sais comment manger un ICloneable
, ou quoi que ce soit la mise en œuvre ou d'hériter d'elle, d'une certaine façon.
Et, je sais aussi comment manger un IConvertible
, ou quoi que ce soit la mise en œuvre ou d'hériter d'elle, d'une certaine façon.
Cependant, il n'affirme jamais ce qu'il devrait faire s'il reçoit sur son plateau quelque chose qui est à la fois un ICloneable
et IConvertible
. Cela ne cause pas le compilateur toute la douleur si elle est donnée une instance d' HungryWolf
, car il peut dire avec certitude "Hey, je ne sais pas quoi faire!". Mais il donnera le compilateur de la douleur quand elle est donnée à l' ICanEat<string>
de l'instance. Le compilateur n'a aucune idée de ce que le type réel de l'objet dans la variable est, seulement qu'il n'a certainement mettre en oeuvre ICanEat<string>
.
Malheureusement, quand un HungryWolf
est stockée dans cette variable, il met en œuvre de manière ambiguë que même interface que deux fois. Alors certes, nous ne pouvons pas jeter une erreur lors de l'appel d' ICanEat<string>.Eat(string)
, car cette méthode existe et serait parfaitement valable pour de nombreux autres objets qui pourraient être placés dans l' ICanEat<string>
variable (batwad déjà mentionné dans une de ses réponses).
En outre, bien que le compilateur pourrait se plaindre que la cession d'un HungryWolf
objet à un ICanEat<string>
variable est ambigu, il ne peut pas l'empêcher de se produire en deux étapes. Un HungryWolf
peuvent être attribués à un ICanEat<IConvertible>
variable, qui peut être transmise à d'autres méthodes et affectés dans un ICanEat<string>
variable. Ces deux sont parfaitement légales affectations et il serait impossible pour le compilateur pour se plaindre de l'un des deux.
Ainsi, la première option consiste à interdire l' HungryWolf
classe à partir de la mise en œuvre de deux ICanEat<IConvertible>
et ICanEat<ICloneable>
lorsque ICanEat
s'paramètre de type générique est contravariant, puisque ces deux interfaces pourrait unifier. Toutefois, cela supprime la capacité de code quelque chose d'utile avec aucune autre solution.
Option deux, malheureusement, aurait besoin d'un changement pour le compilateur, tant à l'IL et de la CLR. Cela permettrait à l' HungryWolf
classe pour implémenter deux interfaces, mais il faudrait aussi que la mise en œuvre de l'interface ICanEat<IConvertible & ICloneable>
interface, où le paramètre de type générique implémente deux interfaces. Ce n'est probablement pas le meilleur de la syntaxe(ce qui ne la signature de la présente Eat(T)
méthode ressembler, Eat(IConvertible & ICloneable food)
?). Probablement, une meilleure solution serait d'une auto-généré de type générique, sur la mise en œuvre de la classe, de sorte que la définition de la classe serait quelque chose comme:
class HungryWolf:
ICanEat<ICloneable>,
ICanEat<IConvertible>,
ICanEat<TGenerated_ICloneable_IConvertible>
where TGenerated_ICloneable_IConvertible: IConvertible, ICloneable {
// implementation
}
IL faudrait alors que changé, pour être en mesure de permettre l'implémentation de l'interface types de être construits comme des classes génériques pour un callvirt
enseignement:
.class auto ansi nested private beforefieldinit HungryWolf
extends
[mscorlib]System.Object
implements
class NamespaceOfApp.Program/ICanEat`1<class [mscorlib]System.ICloneable>,
class NamespaceOfApp.Program/ICanEat`1<class [mscorlib]System.IConvertible>,
class NamespaceOfApp.Program/ICanEat`1<class ([mscorlib]System.IConvertible, [mscorlib]System.ICloneable>)!TGenerated_ICloneable_IConvertible>
Le CLR aurait alors de processus d' callvirt
instructions par la construction d'une interface de mise en œuvre pour l' HungryWolf
avec string
que le paramètre de type générique pour TGenerated_ICloneable_IConvertible
, et de vérifier pour voir si elle correspond mieux que les autres implémentations d'interface.
Pour la covariance, tout cela serait plus simple, car les interfaces supplémentaires doivent être mises en œuvre n'ont pas à être des paramètres de type générique avec des contraintes , mais simplement la plupart des dérivés de type de base entre les deux autres types, ce qui est connu au moment de la compilation.
Si la même interface est mis en œuvre plus de deux fois, puis le nombre d'interfaces supplémentaires doivent être mises en œuvre croît de façon exponentielle, mais ce serait le coût de la flexibilité et de sécurité du type de mise en œuvre de plusieurs contravariant(ou covariants) sur une seule classe.
Je doute que cela va le faire dans le cadre, mais ce serait ma solution préférée, surtout depuis la nouvelle complexité du langage serait toujours autonome de la classe qui le souhaite de ce qui est actuellement dangereux.
edit:
Grâce Jeppe pour me rappeler que la covariance est pas plus simple que la contravariance, en raison du fait que les interfaces communes doivent également être prises en compte. Dans le cas d' string
et char[]
, l'ensemble des plus grands points communs serait {object
, ICloneable
, IEnumerable<char>
} (IEnumerable
est couvert par IEnumerable<char>
).
Toutefois, cela nécessiterait une nouvelle syntaxe de l'interface paramètre de type générique contrainte, pour indiquer que le paramètre de type générique ne doit
- hériter de la classe spécifiée ou une classe de mise en œuvre au moins une des interfaces spécifiées
- mettre en œuvre au moins une des interfaces spécifiées
Peut-être quelque chose comme:
interface ICanReturn<out T> where T: class {
}
class ReturnStringsOrCharArrays:
ICanReturn<string>,
ICanReturn<char[]>,
ICanReturn<TGenerated_String_ArrayOfChar>
where TGenerated_String_ArrayOfChar: object|ICloneable|IEnumerable<char> {
}
Le paramètre de type générique TGenerated_String_ArrayOfChar
dans ce cas(où un ou plusieurs interfaces sont communes) doivent toujours être traités comme des object
, même si la classe de base commune a déjà dérivé d' object
; parce que le type le plus commun peut mettre en œuvre une interface commune, sans hériter de la classe de base commune.