Question

Si j'ai ces deux classes:

class A {}
class B : A {}

et je fais une liste < A > mais je veux ajouter une liste < B > en appelant List < A > .AddRange (List < B >) mais le compilateur refuse:

Argument '1': cannot convert from 'System.Collections.Generic.List<A>'
to 'System.Collections.Generic.IEnumerable<B>

que je comprends tout à fait parce que IEnumerable < B > n'hérite pas de IEnumerable < A > ;, son type générique a l'héritage.

Ma solution consiste à énumérer dans la liste < B > et ajouter individuellement des éléments, car la liste < A > .Add (élément A) fonctionnera avec les éléments B:

foreach(B item in listOfBItems)
{
    listOfAItems.Add(item);
}

Cependant, c'est plutôt non-expressif car ce que je veux, c'est juste AddRange.

Je pourrais utiliser

List<B>.ConvertAll<A>(delegate(B item) {return (A)item;});

mais c'est inutilement compliqué et impropre parce que je ne me convertis pas, je lance.

Question : Si je devais écrire ma propre collection semblable à une liste, quelle méthode ajouterais-je pour me permettre de copier une collection de B dans une collection de A en une ligne semblable à la liste < A > .AddRange (liste < B >) et conservent une sécurité de type maximale. (Et par maximum, je veux dire que l'argument est à la fois une collection et une vérification de type héritage.)

Était-ce utile?

La solution

En effet, les types génériques ne sont pas des variantes pour le moment. En C # 4.0, IEnumerable <B & Gt; sera convertible en IEnumerable <A > si B est convertible en A via une conversion de référence . Pour plus de détails sur la conception de cette fonctionnalité, voir:

http://blogs.msdn.com/ ericlippert / archive / tags / Covariance + et + Contravariance / default.aspx

Autres conseils

Cela ne fonctionne malheureusement pas car les génériques de .net ne supportent pas (encore) la covariance.

Vous pouvez toutefois créer une petite méthode ou classe d'assistance pour résoudre ce problème.

Si vous implémentez votre propre classe de liste, vous pouvez ajouter une covariance à l'aide d'un paramètre générique supplémentaire:

class MyList<T> {
    void AddRange<U>(IEnumerable<U> items) where U: T {
        foreach (U item in items) {
            Add(item);
        }
    }
}

Ne pouvez-vous pas simplement faire:

listOfAItems.AddRange(listOfBItems.Cast<A>());

J'ai pu réaliser cela avec LINQ ...

listOfAItems.AddRange(listOfBItems.Cast<A>());

Si vous vous trouvez dans une situation où les types génériques ne sont pas des variantes, la méthode d'extension suivante peut vous faciliter la vie:

public static void AddRange<TList,TOther>(this List<TList> list, IEnumerable<TOther> collection) where TOther: TList {
    foreach(TOther e in collection) {
        list.Add(e);
    }
}

Au lieu de devoir dériver de List<T> ou d'avoir cette méthode dans une classe d'utilitaires, son utilisation en tant que méthode d'extension simplifie l'utilisation. Vous pouvez également tirer parti de l'inférence pour que cet appel précédemment non valide devienne valide sans aucune modification:

List<Animal> animals;
List<Dog> dogs;
animals.AddRange(dogs);

La seule chose que je peux trouver est la suivante

public class MyList<T> : List<T>
{
    public void AddRange<Tother>(IEnumerable<Tother> col)
        where Tother: T
    {    
        foreach (Tother item in col)
        {
            this.Add(item);
        }
    }
}

Appeler cela signifie faire MyList < A > .AddRange < B > ((MyList < B >)). Cela échoue si l'argument n'est pas énumérable ou si l'héritage de type ne fonctionne pas, il répond donc aux exigences de sécurité de type maximales de ma question.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top