Pergunta

Se eu tiver essas duas classes:

class A {}
class B : A {}

e eu faço um List<A> mas quero adicionar um List<B> chamando List<A>.AddRange(List<B>) mas o compilador recusa:

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

o que eu entendo perfeitamente porque IEnumerable<B> não herda de IEnumerable<A>, seu tipo genérico possui a herança.

Minha solução é enumerar através de List<B> e adicionar itens individualmente porque List<A>.Add(A item) funcionará com itens B:

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

No entanto, isso não é expressivo porque o que eu quero é apenas AddRange.

eu poderia usar

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

mas isso é desnecessariamente complicado e um nome impróprio porque não estou convertendo, estou lançando.

Pergunta:Se eu fosse escrever minha própria coleção do tipo Lista, qual método eu adicionaria a ela que me permitiria copiar uma coleção de B em uma coleção de A como uma linha semelhante a List<A>.AddRange(List<B >) e manter a máxima segurança de tipo.(E por máximo quero dizer que o argumento é uma verificação de herança de coleção e de tipo.)

Foi útil?

Solução

Na verdade, os tipos genéricos não são variantes no momento.Em C# 4.0, IEnumerable<B> será conversível para IEnumerable<A> se B é conversível em A por meio de uma conversão de referência.Para alguns detalhes sobre o design deste recurso, consulte:

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

Outras dicas

Infelizmente, isso não funciona porque os genéricos em .net (ainda) não suportam covariância.

No entanto, você pode criar um pequeno método ou classe auxiliar para superar esse problema.

Se você implementar sua própria classe de lista, poderá adicionar covariância usando um parâmetro genérico adicional:

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

Você não pode simplesmente fazer:

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

Consegui fazer isso usando LINQ ...

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

Caso você se encontre em uma situação em que os tipos genéricos não sejam variantes, o seguinte método de extensão pode facilitar sua vida:

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

Em vez de ter que derivar List<T> ou ter esse método em alguma classe utilitária, usá-lo como método de extensão simplifica o uso.Você também pode lucrar com a inferência, então esta chamada anteriormente inválida se tornará válida sem qualquer modificação:

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

A única coisa que consigo pensar é isso

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

Chamá-lo significa fazer MyList<A>.AddRange<B>(MyList<B>).Isso falhará se o argumento não for enumerável ou se a herança de tipo não funcionar, satisfazendo o requisito máximo de segurança de tipo da minha pergunta.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top