Quando deve ou não eu estar usando restrições de tipo genérico?
-
10-07-2019 - |
Pergunta
Eu tenho uma classe base:
public abstract class StuffBase
{
public abstract void DoSomething();
}
E duas classes derivadas
public class Stuff1 : StuffBase
{
public void DoSomething()
{
Console.WriteLine("Stuff 1 did something cool!");
}
public Stuff1()
{
Console.WriteLine("New stuff 1 reporting for duty!");
}
}
public class Stuff2 : StuffBase
{
public void DoSomething()
{
Console.WriteLine("Stuff 2 did something cool!");
}
public Stuff1()
{
Console.WriteLine("New stuff 2 reporting for duty!");
}
}
Ok, agora dizem que eu tenho uma lista de itens:
var items = new List<StuffBase>();
items.Add(new Stuff1());
items.Add(new Stuff2());
e eu quero que eles para chamar seu método DoSomething (). Eu poderia esperar apenas iterate a lista e chamar seu método DoSomething (), então vamos dizer que eu tenho um método para fazer isso chamado AllDoSomething () que interage apenas sobre a lista e faz o trabalho:
public static void AllDoSomething(List<StuffBase> items)
{
items.ForEach(i => i.DoSomething());
}
Qual é a diferença prática do método seguinte?
public static void AllDoSomething<T>(List<T> items) where T: StuffBase
{
items.ForEach(i => i.DoSomething());
}
Ambos os métodos parecem, em termos reais, apesar de ser sintaticamente diferente, estar fazendo a mesma coisa.
Eles são apenas maneiras diferentes de fazer a mesma coisa? Eu entendo genéricos e tipo restrições, mas não pode ver porque eu usaria uma forma sobre o outro neste caso.
Solução
Isso ocorre porque como ainda, C # não suporta Covariance .
Mais formalmente, em C # v2.0 se T é um subtipo de U, então T [] é um subtipo de U [], mas G não é um subtipo de G (Onde G é qualquer tipo genérico). No terminologia teoria do tipo, descrevemos este comportamento dizendo que série C # tipos são “covariante” e genérico tipos são “invariável”.
Referência: http: // blogs. msdn.com/rmbyers/archive/2005/02/16/375079.aspx
Se você tem o seguinte método:
public static void AllDoSomething(List<StuffBase> items)
{
items.ForEach(i => i.DoSomething());
}
var items = new List<Stuff2>();
x.AllDoSomething(items); //Does not compile
Onde como se você usar o tipo de restrição genérica, ele vai.
Para mais informações sobre Covariância e Contravariance], veja série de mensagens de Eric Lippert .
Outras mensagens vale a pena ler:
- http://www.pabich.eu/blog/archive/2008/02/12/c-generics---parameter-variance-its-constraints-and-how-it.aspx
- http://blogs.msdn.com/rmbyers /archive/2006/06/01/613690.aspx
- http://msdn.microsoft.com/ en-us / library / ms228359 (VS.80) .aspx
- http://www.csharp411.com/convert-between-generic- ienumerablet /
- http://research.microsoft.com/apps/pubs /default.aspx?id=64042
- Por que não List
= List ?
Outras dicas
Suponha que você tenha uma lista:
List<Stuff1> l = // get from somewhere
Agora tente:
AllDoSomething(l);
Com a versão genérica, será permitido. Com o não-genérico, ele não vai. Essa é a diferença essencial. Uma lista de Stuff1
não é uma lista de StuffBase
. Mas, no caso genérico, você não exigem que ele seja exatamente uma lista de StuffBase
, por isso é mais flexível.
Você poderia resolver isso primeiro copiar sua lista de Stuff1
em uma lista de StuffBase
, para torná-lo compatível com a versão não-genérico. Mas, em seguida, suponha que você tinha um método:
List<T> TransformList<T>(List<T> input) where T : StuffBase
{
List<T> output = new List<T>();
foreach (T item in input)
{
// examine item and decide whether to discard it,
// make new items, whatever
}
return output;
}
Sem genéricos, você poderia aceitar uma lista de StuffBase
, mas você teria, então, para retornar uma lista de StuffBase
. O chamador teria de usar moldes se soubessem que os itens foram realmente de um tipo derivado. Assim, os genéricos permitem preservar o tipo real de um argumento e canalizá-la através do método para o tipo de retorno.
No exemplo que você forneceu não há diferença, mas tente o seguinte:
List<Stuff1> items = new List<Stuff1>();
items.Add(new Stuff1());
AllDoSomething(items);
AllDoSomething<StuffBase>(items);
A primeira chamada funciona bem, mas o segundo não compila devido a covariância genérico