Vários tipos genéricos em uma lista

153

Provavelmente isso não é possível, mas eu tenho esta classe:

public class Metadata<DataType> where DataType : struct
{
    private DataType mDataType;
}

Há mais do que isso, mas vamos simplificar. O tipo genérico (DataType) é limitado aos tipos de valor pela instrução where. O que eu quero fazer é ter uma lista desses objetos de metadados de tipos variados (DataType). Tal como:

List<Metadata> metadataObjects;
metadataObjects.Add(new Metadata<int>());
metadataObjects.Add(new Metadata<bool>());
metadataObjects.Add(new Metadata<double>());

Isso é possível?

Carl
fonte
24
Gostaria de saber se há algum benefício real para as abordagens nas respostas abaixo em comparação com apenas usando um List<object>? Eles não param de boxe / desembalam, não eliminam a necessidade de transmissão e, finalmente, você está recebendo um Metadataobjeto que não diz nada sobre o real DataType, eu estava procurando uma solução para resolver esses problemas. Se você declarar uma interface / classe, apenas para poder colocar o tipo genérico de implementação / derivado em uma lista genérica, quão diferente é isso do que usar List<object>outro que não tenha uma camada sem sentido?
precisa
9
A classe base abstrata e a interface fornecem um grau de controle, restringindo o tipo de elementos que podem ser adicionados à lista. Também não consigo ver como o boxe entra nisso.
0b101010
3
Obviamente, se você estiver usando o .NET v4.0 ou superior, a covariância é a solução. List<Metadata<object>>faz o truque.
0101010
2
@ 0b101010 Eu estava pensando o mesmo, mas infelizmente a variação não é permitida nos tipos de valor. Como o OP tem uma structrestrição, ele não funciona aqui. Veja
nawfal
@ 0b101010, Ambos apenas restringem tipos de referência, qualquer tipo de valor interno e qualquer estrutura ainda podem ser adicionados. Além disso, no final, você tem uma lista de MetaDatatipos de referência em vez dos tipos de valor originais sem informações (tempo de compilação) sobre o tipo de valor subjacente de cada elemento, que é efetivamente "boxing".
Saeb Amini 29/09

Respostas:

195
public abstract class Metadata
{
}

// extend abstract Metadata class
public class Metadata<DataType> : Metadata where DataType : struct
{
    private DataType mDataType;
}
leppie
fonte
5
Uau! Eu realmente não achei que isso fosse possível! Você é um salva-vidas, cara!
912 Carl
2
+10 para isso! Não sei por que isso compila .. Exatamente o que eu precisava!
Odys
Eu tenho um problema semelhante, mas minha classe genérica se estende de outra classe genérica, portanto não posso usar sua solução ... alguma idéia sobre uma correção para essa situação?
21812 Sheridan
10
Existe um benefício para essa abordagem em comparação com uma simples List<object>? por favor, olhe meu comentário postado na pergunta do OP.
Saeb Amini
11
@SaebAmini Uma lista <object> não mostra nenhuma intenção a um desenvolvedor, nem impede que um desenvolvedor atire no pé adicionando erroneamente algum objeto que não seja MetaData à lista. Usando uma Lista <MetaData>, entende-se o que a lista deve conter. O MetaData provavelmente terá algumas propriedades / métodos públicos que não foram mostrados nos exemplos acima. Acessar aqueles através de objeto exigiria um elenco complicado.
Buzz
92

Seguindo a resposta de leppie, por que não fazer MetaDatauma interface:

public interface IMetaData { }

public class Metadata<DataType> : IMetaData where DataType : struct
{
    private DataType mDataType;
}
bruno conde
fonte
Alguém pode me dizer por que essa abordagem é melhor?
Lazlo
34
Como nenhuma funcionalidade comum é compartilhada - por que desperdiçar uma classe base nisso? Uma interface é suficiente
flq
2
Porque você pode implementar interfaces em struct.
Damian Leszczyński - Vash 01/01
2
A herança de classe usando métodos virtuais, no entanto, é aproximadamente 1,4x mais rápida que os métodos de interface. Portanto, se você planeja implementar métodos / propriedades MetaData (virtuais) não genéricos no MetaData <DataType>, escolha uma classe abstrata em vez de uma interface, se o desempenho for uma preocupação. Caso contrário, o uso de uma interface pode ser mais flexível.
TamusJRoyce 04/04
30

Também usei uma versão não genérica, usando a newpalavra-chave:

public interface IMetadata
{
    Type DataType { get; }

    object Data { get; }
}

public interface IMetadata<TData> : IMetadata
{
    new TData Data { get; }
}

A implementação explícita da interface é usada para permitir os dois Datamembros:

public class Metadata<TData> : IMetadata<TData>
{
    public Metadata(TData data)
    {
       Data = data;
    }

    public Type DataType
    {
        get { return typeof(TData); }
    }

    object IMetadata.Data
    {
        get { return Data; }
    }

    public TData Data { get; private set; }
}

Você pode derivar uma versão que segmenta tipos de valor:

public interface IValueTypeMetadata : IMetadata
{

}

public interface IValueTypeMetadata<TData> : IMetadata<TData>, IValueTypeMetadata where TData : struct
{

}

public class ValueTypeMetadata<TData> : Metadata<TData>, IValueTypeMetadata<TData> where TData : struct
{
    public ValueTypeMetadata(TData data) : base(data)
    {}
}

Isso pode ser estendido a qualquer tipo de restrições genéricas.

Bryan Watts
fonte
4
+1 só porque você está mostrando como usá-lo ( DataTypee object Dataajudou muito)
Odys
4
Parece que não consigo escrever, por exemplo Deserialize<metadata.DataType>(metadata.Data);. Ele diz que não é possível resolver os metadados do símbolo . Como recuperar o DataType para usá-lo para um método genérico?
Cœur