C #: múltiples tipos genéricos en una lista

153

Probablemente esto no sea posible, pero tengo esta clase:

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

Hay más, pero seamos simples. El tipo genérico (DataType) está limitado a los tipos de valor mediante la instrucción where. Lo que quiero hacer es tener una lista de estos objetos de metadatos de diferentes tipos (DataType). Como:

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

¿Es esto posible?

Carl
fuente
24
Me pregunto si hay algún beneficio real para los enfoques en las respuestas a continuación en comparación con solo usar un List<object>? No detendrán el boxeo / desempaquetado, no eliminarán la necesidad de lanzar y, en última instancia, está obteniendo un Metadataobjeto que no le dice nada sobre lo real DataType, estaba buscando una solución para abordar esos problemas. Si va a declarar una interfaz / clase, solo por el hecho de poder poner el tipo genérico implementado / derivado en una lista genérica, ¿qué tan diferente es eso de usar List<object>otro que no sea una capa sin sentido?
Saeb Amini
9
Tanto la clase base abstracta como la interfaz proporcionan un grado de control al restringir el tipo de elementos que se pueden agregar a la lista. Tampoco puedo ver cómo el boxeo entra en esto.
0b101010
3
Por supuesto, si está utilizando .NET v4.0 o superior, entonces la covarianza es la solución. List<Metadata<object>>Hace el truco.
0b101010
2
@ 0b101010 Estaba pensando lo mismo, pero desafortunadamente no se permite la variación en los tipos de valor. Dado que OP tiene una structrestricción, no funciona aquí. Ver
nawfal
@ 0b101010, Ambos solo restringen los tipos de referencia, cualquier tipo de valor incorporado y cualquier estructura aún se pueden agregar. Además, al final, tiene una lista de MetaDatatipos de referencia en lugar de sus tipos de valores originales sin información (tiempo de compilación) sobre el tipo de valor subyacente de cada elemento, eso es efectivamente "boxing".
Saeb Amini

Respuestas:

195
public abstract class Metadata
{
}

// extend abstract Metadata class
public class Metadata<DataType> : Metadata where DataType : struct
{
    private DataType mDataType;
}
leppie
fuente
55
¡Guauu! ¡Realmente no pensé que eso fuera posible! Eres un salvavidas, hombre!
Carl
2
+10 por esto! No sé por qué esto se compila ... ¡Exactamente lo que necesitaba!
Odys
Tengo un problema similar, pero mi clase genérica se extiende desde otra clase genérica, por lo que no puedo usar su solución ... ¿alguna idea sobre una solución para esta situación?
Sheridan
10
¿Hay algún beneficio en este enfoque en comparación con un simple List<object>? mira mi comentario publicado bajo la pregunta de OP.
Saeb Amini
11
@SaebAmini Una lista <objeto> no muestra ninguna intención a un desarrollador, ni impide que un desarrollador se dispare en el pie al agregar erróneamente algún objeto que no sea MetaData a la lista. Al usar una Lista <MetaData> se entiende lo que debe contener la lista. Lo más probable es que MetaData tenga algunas propiedades / métodos públicos que no se han mostrado en los ejemplos anteriores. Acceder a ellos a través del objeto requeriría un yeso engorroso.
Buzz
92

Siguiendo la respuesta de leppie, ¿por qué no hacer MetaDatauna interfaz?

public interface IMetaData { }

public class Metadata<DataType> : IMetaData where DataType : struct
{
    private DataType mDataType;
}
bruno conde
fuente
¿Alguien puede decirme por qué este enfoque es mejor?
Lazlo
34
Debido a que no se comparte ninguna funcionalidad común, ¿por qué desperdiciar una clase base en eso entonces? Una interfaz es suficiente
flq
2
Porque puedes implementar interfaces en struct.
Damian Leszczyński - Vash
2
Sin embargo, la herencia de clases utilizando métodos virtuales es aproximadamente 1,4 veces más rápida que los métodos de interfaz. Entonces, si planea implementar métodos / propiedades MetaData (virtuales) no genéricos en MetaData <DataType>, elija una clase abstracta en lugar de una interfaz, si el rendimiento es una preocupación. De lo contrario, el uso de una interfaz puede ser más flexible.
TamusJRoyce
30

También he usado una versión no genérica, usando la newpalabra clave:

public interface IMetadata
{
    Type DataType { get; }

    object Data { get; }
}

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

La implementación explícita de la interfaz se utiliza para permitir que ambos Datamiembros:

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; }
}

Podría derivar una versión orientada a los 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)
    {}
}

Esto puede extenderse a cualquier tipo de restricciones genéricas.

Bryan Watts
fuente
44
+1 solo porque está mostrando cómo usarlo ( DataTypey object Dataayudó mucho)
Odys
44
Parece que no puedo escribir, por ejemplo Deserialize<metadata.DataType>(metadata.Data);. Me dice que no puedo resolver los metadatos de los símbolos . ¿Cómo recuperar el DataType para usarlo para un método genérico?
Cœur