C #: più tipi generici in un elenco


153

Questo probabilmente non è possibile, ma ho questa classe:

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

C'è di più, ma manteniamolo semplice. Il tipo generico (DataType) è limitato ai tipi di valore dall'istruzione where. Quello che voglio fare è avere un elenco di questi oggetti Metadata di vario tipo (DataType). Ad esempio:

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

È anche possibile?


24
Mi chiedo se ci sia qualche reale vantaggio negli approcci nelle risposte di seguito rispetto al solo utilizzo di un List<object>? Non smetteranno di boxe / unboxing, non rimuoveranno la necessità del casting e, alla fine, stai ottenendo un Metadataoggetto che non ti dice nulla sul reale DataType, stavo cercando una soluzione per affrontare questi problemi. Se hai intenzione di dichiarare un'interfaccia / classe, solo per essere in grado di mettere il tipo generico di implementazione / derivato in un elenco generico, quanto è diverso dall'usare un List<object>altro che avere un livello insignificante?
Saeb Amini,

9
Sia la classe base astratta che l'interfaccia forniscono un certo grado di controllo limitando il tipo di elementi che possono essere aggiunti all'elenco. Inoltre non riesco a vedere come la boxe entra in questo.
0b101010,

3
Naturalmente, se si utilizza .NET v4.0 o versione successiva, la soluzione è la covarianza. List<Metadata<object>>fa il trucco.
0b101010,

2
@ 0b101010 Stavo pensando lo stesso, ma sfortunatamente la varianza non è consentita sui tipi di valore. Poiché OP ha un structvincolo, qui non funziona. Vedi
nawfal,

@ 0b101010, Entrambi possono limitare solo i tipi di riferimento, è possibile aggiungere qualsiasi tipo di valore incorporato e qualsiasi struttura. Inoltre, alla fine, hai un elenco di MetaDatatipi di riferimento invece dei tuoi tipi di valore originali senza informazioni (tempo di compilazione) sul tipo di valore sottostante di ciascun elemento, che è effettivamente "boxing".
Saeb Amini,

Risposte:


195
public abstract class Metadata
{
}

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

5
Wow! Non pensavo davvero che fosse possibile! Stai salvando la vita, amico!
Carl,

2
+10 per questo! Non so perché questo compili .. Esattamente quello di cui avevo bisogno!
Odys,

Ho un problema simile, ma la mia classe generica si estende da un'altra classe generica, quindi non posso usare la tua soluzione ... qualche idea su una soluzione per questa situazione?
Sheridan,

10
C'è un vantaggio in questo approccio rispetto a un semplice List<object>? guarda il mio commento pubblicato sotto la domanda di OP.
Saeb Amini,

11
@SaebAmini Una lista <oggetto> non mostra alcuna intenzione a uno sviluppatore, né impedisce a uno sviluppatore di spararsi nel piede aggiungendo erroneamente un oggetto non MetaData alla lista. Usando un Elenco <MetaData> si comprende cosa dovrebbe contenere l'elenco. Molto probabilmente MetaData avrà alcune proprietà / metodi pubblici che non sono stati mostrati negli esempi precedenti. Accedere a quelli attraverso l'oggetto richiederebbe un cast ingombrante.
Buzz

92

Seguendo la risposta di leppie, perché non creare MetaDataun'interfaccia:

public interface IMetaData { }

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

Qualcuno può dirmi perché questo approccio è migliore?
Lazlo,

34
Perché nessuna funzionalità comune è condivisa - perché sprecare una classe base su quella allora? È sufficiente un'interfaccia
flq,

2
Perché è possibile implementare interfacce in struct.
Damian Leszczyński - Vash

2
L'ereditarietà delle classi che utilizza metodi virtuali, tuttavia, è circa 1,4 volte più veloce dei metodi di interfaccia. Pertanto, se prevedi di implementare metodi / proprietà MetaData (virtuali) non generici in MetaData <DataType>, scegli una classe astratta anziché un'interfaccia, se le prestazioni sono un problema. Altrimenti, l'utilizzo di un'interfaccia può essere più flessibile.
TamusJRoyce,

30

Ho anche usato una versione non generica, usando la newparola chiave:

public interface IMetadata
{
    Type DataType { get; }

    object Data { get; }
}

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

L'implementazione esplicita dell'interfaccia viene utilizzata per consentire a entrambi i Datamembri:

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

È possibile derivare una versione destinata ai tipi di valore:

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)
    {}
}

Questo può essere esteso a qualsiasi tipo di vincolo generico.


4
+1 solo perché stai mostrando come usarlo ( DataTypee ti ha object Dataaiutato molto)
Odys,

4
Non riesco a scrivere per esempio Deserialize<metadata.DataType>(metadata.Data);. Mi dice che non è possibile risolvere i metadati dei simboli . Come recuperare DataType per usarlo per un metodo generico?
Cœur
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.