Alors que nous pouvons hériter de la classe / interface de base, pourquoi ne pouvons-nous pas déclarer un List<>
utilisant la même classe / interface?
interface A
{ }
class B : A
{ }
class C : B
{ }
class Test
{
static void Main(string[] args)
{
A a = new C(); // OK
List<A> listOfA = new List<C>(); // compiler Error
}
}
Y a-t-il un moyen de contourner?
Réponses:
La façon de faire ce travail consiste à parcourir la liste et à convertir les éléments. Cela peut être fait en utilisant ConvertAll:
Vous pouvez également utiliser Linq:
la source
ConvertAll
ouCast
?Tout d'abord, arrêtez d'utiliser des noms de classe impossibles à comprendre comme A, B, C.Utilisez Animal, Mammifère, Girafe, Nourriture, Fruit, Orange ou quelque chose où les relations sont claires.
Votre question est alors "pourquoi ne puis-je pas attribuer une liste de girafes à une variable de type liste d'animal, puisque je peux attribuer une girafe à une variable de type animal?"
La réponse est: supposons que vous puissiez. Qu'est-ce qui pourrait alors mal tourner?
Eh bien, vous pouvez ajouter un tigre à une liste d'animaux. Supposons que nous vous permettions de mettre une liste de girafes dans une variable contenant une liste d'animaux. Ensuite, vous essayez d'ajouter un tigre à cette liste. Ce qui se produit? Voulez-vous que la liste des girafes contienne un tigre? Voulez-vous un crash? ou voulez-vous que le compilateur vous protège du plantage en rendant l'attribution illégale en premier lieu?
Nous choisissons ce dernier.
Ce type de conversion est appelé une conversion «covariante». En C # 4, nous vous autoriserons à effectuer des conversions covariantes sur les interfaces et les délégués lorsque la conversion est connue pour être toujours sûre . Voir les articles de mon blog sur la covariance et la contravariance pour plus de détails. (Il y en aura un nouveau sur ce sujet le lundi et le jeudi de cette semaine.)
la source
IEnumerable
au lieu d'unList
? c'est-à-dire:List<Animal> listAnimals = listGiraffes as List<Animal>;
n'est pas possible, maisIEnumerable<Animal> eAnimals = listGiraffes as IEnumerable<Animal>
fonctionne.IEnumerable<T>
etIEnumerator<T>
sont tous deux marqués comme sûrs pour la covariance, et le compilateur l'a vérifié.Pour citer la grande explication d'Eric
Mais que faire si vous souhaitez choisir un crash d'exécution au lieu d'une erreur de compilation? Vous utiliseriez normalement Cast <> ou ConvertAll <> mais alors vous aurez 2 problèmes: Cela créera une copie de la liste. Si vous ajoutez ou supprimez quelque chose dans la nouvelle liste, cela ne sera pas reflété dans la liste d'origine. Et deuxièmement, il y a une grosse pénalité en termes de performances et de mémoire car cela crée une nouvelle liste avec les objets existants.
J'ai eu le même problème et j'ai donc créé une classe wrapper qui peut lancer une liste générique sans créer une liste entièrement nouvelle.
Dans la question d'origine, vous pouvez alors utiliser:
et ici la classe wrapper (+ une méthode d'extension CastList pour une utilisation facile)
la source
Si vous utilisez à la
IEnumerable
place, cela fonctionnera (au moins en C # 4.0, je n'ai pas essayé les versions précédentes). Ce n'est qu'un casting, bien sûr, ce sera toujours une liste.Au lieu de -
List<A> listOfA = new List<C>(); // compiler Error
Dans le code d'origine de la question, utilisez -
IEnumerable<A> listOfA = new List<C>(); // compiler error - no more! :)
la source
List<A> listOfA = new List<C>(); // compiler Error
dans le code original de la question, entrezIEnumerable<A> listOfA = new List<C>(); // compiler error - no more! :)
Quant à savoir pourquoi cela ne fonctionne pas, il pourrait être utile de comprendre la covariance et la contravariance .
Juste pour montrer pourquoi cela ne devrait pas fonctionner, voici une modification du code que vous avez fourni:
Cela devrait-il fonctionner? Le premier élément de la liste est de type "B", mais le type de l'élément DerivedList est C.
Maintenant, supposons que nous voulons vraiment juste créer une fonction générique qui opère sur une liste d'un type qui implémente A, mais nous ne nous soucions pas de quel type c'est:
la source
Vous ne pouvez diffuser que des listes en lecture seule. Par exemple:
Et vous ne pouvez pas le faire pour les listes qui prennent en charge l'enregistrement des éléments. La raison en est:
Et maintenant? Rappelez-vous que listObject et listString sont la même liste en fait, donc listString a maintenant un élément objet - cela ne devrait pas être possible et ce n'est pas le cas.
la source
J'aime personnellement créer des bibliothèques avec des extensions aux classes
la source
Parce que C # n'autorise pas ce type de
héritageconversion pour le moment .la source
Ceci est une extension de la réponse brillante de BigJim .
Dans mon cas, j'avais une
NodeBase
classe avec unChildren
dictionnaire, et j'avais besoin d'un moyen de faire des recherches génériques O (1) à partir des enfants. J'essayais de renvoyer un champ de dictionnaire privé dans le getter deChildren
, donc évidemment je voulais éviter une copie / itération coûteuse. Par conséquent, j'ai utilisé le code de Bigjim pour convertir leDictionary<whatever specific type>
en un génériqueDictionary<NodeBase>
:Cela a bien fonctionné. Cependant, j'ai finalement rencontré des limitations indépendantes et j'ai fini par créer une
FindChild()
méthode abstraite dans la classe de base qui ferait les recherches à la place. Il s'est avéré que cela a éliminé le besoin du dictionnaire coulé en premier lieu. (J'ai pu le remplacer par un simpleIEnumerable
pour mes besoins.)La question que vous pourriez vous poser (surtout si les performances sont un problème qui vous interdit d'utiliser
.Cast<>
ou.ConvertAll<>
) est:"Ai-je vraiment besoin de convertir toute la collection, ou puis-je utiliser une méthode abstraite pour détenir les connaissances spéciales nécessaires pour effectuer la tâche et éviter ainsi d'accéder directement à la collection?"
Parfois, la solution la plus simple est la meilleure.
la source
Vous pouvez également utiliser le
System.Runtime.CompilerServices.Unsafe
package NuGet pour créer une référence au mêmeList
:Compte tenu de l'exemple ci-dessus, vous pouvez accéder aux
Hammer
instances existantes dans la liste à l'aide de latools
variable. L'ajout d'Tool
instances à la liste lève uneArrayTypeMismatchException
exception car faittools
référence à la même variable quehammers
.la source
J'ai lu tout ce fil et je veux juste souligner ce qui me semble être une incohérence.
Le compilateur vous empêche de faire l'affectation avec les listes:
Mais le compilateur est parfaitement bien avec les tableaux:
L'argument de savoir si l'affectation est connue pour être sûre s'effondre ici. La mission que j'ai faite avec le tableau n'est pas sûre . Pour le prouver, si je continue avec ceci:
J'obtiens une exception d'exécution "ArrayTypeMismatchException". Comment l'expliquer? Si le compilateur veut vraiment m'empêcher de faire quelque chose de stupide, il aurait dû m'empêcher de faire l'affectation du tableau.
la source