Convertir la liste en liste

Bien que nous puissions hériter de la classe / interface de base, pourquoi ne pouvons-nous pas déclarer une List utilisant la même classe / interface?

 interface A { } class B : A { } class C : B { } class Test { static void Main(ssortingng[] args) { A a = new C(); // OK List listOfA = new List(); // comstackr Error } } 

Y a-t-il un moyen de contourner?

La manière de faire ce travail consiste à parcourir la liste et à lancer les éléments. Cela peut être fait en utilisant ConvertAll:

 List listOfA = new List().ConvertAll(x => (A)x); 

Vous pouvez également utiliser Linq:

 List listOfA = new List().Cast().ToList(); 

Tout d’abord, arrêtez d’utiliser des noms de classes impossibles à comprendre comme A, B, C. Utilisez des animaux, des mammifères, des girafes ou de la nourriture, des fruits, de l’orange ou quelque chose dont les relations sont claires.

Votre question est alors “pourquoi ne puis-je pas atsortingbuer une liste de girafes à une variable de type liste d’animal, puisque je peux assigner une girafe à une variable de type animal?”

La réponse est: supposez que vous pourriez. Qu’est-ce qui pourrait alors aller mal?

Eh bien, vous pouvez append 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’append 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 crash en rendant la mission illégale en premier lieu?

Nous choisissons ce dernier.

Ce type de conversion s’appelle une conversion “covariante”. Dans C # 4, nous vous permettons d’effectuer des conversions covariantes sur les interfaces et les delegates 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 une nouvelle sur ce sujet lundi et jeudi de cette semaine.)

Pour citer la grande explication d’Eric

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 crash en rendant la mission illégale en premier lieu? Nous choisissons ce dernier.

Mais que faire si vous voulez choisir un crash à la place d’une erreur de compilation? Vous utiliseriez normalement Cast <> ou ConvertAll <> mais vous aurez alors 2 problèmes: il 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 grande pénalité de performance et de mémoire car il crée une nouvelle liste avec les objects existants.

J’ai eu le même problème et j’ai donc créé une classe wrapper capable de convertir une liste générique sans créer de liste entièrement nouvelle.

Dans la question originale, vous pouvez alors utiliser:

 class Test { static void Main(ssortingng[] args) { A a = new C(); // OK IList listOfA = new List().CastList(); // now ok! } } 

et ici la classe wrapper (+ une méthode d’extention CastList pour une utilisation facile)

 public class CastedList : IList { public IList BaseList; public CastedList(IList baseList) { BaseList = baseList; } // IEnumerable IEnumerator IEnumerable.GetEnumerator() { return BaseList.GetEnumerator(); } // IEnumerable<> public IEnumerator GetEnumerator() { return new CastedEnumerator(BaseList.GetEnumerator()); } // ICollection public int Count { get { return BaseList.Count; } } public bool IsReadOnly { get { return BaseList.IsReadOnly; } } public void Add(TTo item) { BaseList.Add((TFrom)(object)item); } public void Clear() { BaseList.Clear(); } public bool Contains(TTo item) { return BaseList.Contains((TFrom)(object)item); } public void CopyTo(TTo[] array, int arrayIndex) { BaseList.CopyTo((TFrom[])(object)array, arrayIndex); } public bool Remove(TTo item) { return BaseList.Remove((TFrom)(object)item); } // IList public TTo this[int index] { get { return (TTo)(object)BaseList[index]; } set { BaseList[index] = (TFrom)(object)value; } } public int IndexOf(TTo item) { return BaseList.IndexOf((TFrom)(object)item); } public void Insert(int index, TTo item) { BaseList.Insert(index, (TFrom)(object)item); } public void RemoveAt(int index) { BaseList.RemoveAt(index); } } public class CastedEnumerator : IEnumerator { public IEnumerator BaseEnumerator; public CastedEnumerator(IEnumerator baseEnumerator) { BaseEnumerator = baseEnumerator; } // IDisposable public void Dispose() { BaseEnumerator.Dispose(); } // IEnumerator object IEnumerator.Current { get { return BaseEnumerator.Current; } } public bool MoveNext() { return BaseEnumerator.MoveNext(); } public void Reset() { BaseEnumerator.Reset(); } // IEnumerator<> public TTo Current { get { return (TTo)(object)BaseEnumerator.Current; } } } public static class ListExtensions { public static IList CastList(this IList list) { return new CastedList(list); } } 

En ce qui concerne les raisons pour lesquelles 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 un changement au code que vous avez fourni:

 void DoesThisWork() { List DerivedList = new List(); List BaseList = DerivedList; BaseList.Add(new B()); C FirstItem = DerivedList.First(); } 

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 créer une fonction générique qui fonctionne sur une liste de type implémentant A, mais peu importe le type:

 void ThisWorks(List GenericList) where T:A { } void Test() { ThisWorks(new List()); ThisWorks(new List()); } 

Si vous utilisez IEnumerable place, cela fonctionnera (du moins dans C # 4.0, je n’ai pas essayé les versions précédentes). Ceci est juste une dissortingbution, bien sûr, ce sera toujours une liste.

Au lieu de –

List listOfA = new List(); // comstackr Error

Dans le code original de la question, utilisez –

IEnumerable listOfA = new List(); // comstackr error - no more! :)

Vous pouvez uniquement lancer des listes en lecture seule. Par exemple:

 IEnumerable enumOfA = new List();//This works IReadOnlyCollection ro_colOfA = new List();//This works IReadOnlyList ro_listOfA = new List();//This works 

Et vous ne pouvez pas le faire pour les listes qui prennent en charge les éléments de sauvegarde. La raison en est:

 List listSsortingng=new List(); List listObject=(List)listSsortingng;//Assume that this is possible listObject.Add(new object()); 

Et maintenant? Rappelez-vous que listObject et listSsortingng sont en fait la même liste, de sorte que listSsortingng a maintenant un élément d’object – cela ne devrait pas être possible et ce n’est pas le cas.

Parce que C # ne permet pas ce type de inheritance conversion pour le moment .

Ceci est une extension de la réponse shinye de BigJim.

Dans mon cas, j’avais une classe NodeBase avec un dictionnaire Children et j’avais besoin d’un moyen de faire des recherches O (1) des enfants. J’essayais de renvoyer un champ de dictionnaire privé dans le répertoire des Children , donc je voulais évidemment éviter les copies / itérations coûteuses. Par conséquent, j’ai utilisé le code de Bigjim pour convertir le Dictionary en un Dictionary générique Dictionary :

 // Abstract parent class public abstract class NodeBase { public abstract IDictionary Children { get; } ... } // Implementing child class public class RealNode : NodeBase { private Dictionary containedNodes; public override IDictionary Children { // Using a modification of Bigjim's code to cast the Dictionary: return new IDictionary().CastDictionary(); } ... } 

Cela a bien fonctionné. Cependant, j’ai fini par rencontrer des limitations sans rapport et j’ai fini par créer une méthode FindChild() abstraite dans la classe de base qui ferait les recherches à la place. En fin de compte, cela a éliminé le besoin du dictionnaire de casted en premier lieu. (J’ai pu le remplacer par un simple IEnumerable pour mes besoins.)

Donc, la question que vous pourriez vous poser (surtout si les performances vous .Cast<> utiliser .Cast<> ou .ConvertAll<> ) est la suivante:

“Dois-je vraiment lancer la collection entière ou puis-je utiliser une méthode abstraite pour détenir les connaissances spéciales nécessaires à l’exécution de la tâche et éviter ainsi d’accéder directement à la collection?”

Parfois, la solution la plus simple est la meilleure.

J’aime personnellement créer des bibliothèques avec des extensions aux classes

 public static List Cast(List fromlist) where TFrom : class where TTo : class { return fromlist.ConvertAll(x => x as TTo); }