C # - Mehrere generische Typen in einer Liste

152

Dies ist wahrscheinlich nicht möglich, aber ich habe diese Klasse:

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

Es steckt noch mehr dahinter, aber lassen Sie es uns einfach halten. Der generische Typ (DataType) ist durch die where-Anweisung auf Werttypen beschränkt. Ich möchte eine Liste dieser Metadatenobjekte unterschiedlichen Typs (Datentyp) haben. Sowie:

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

Ist das überhaupt möglich?

Carl
quelle
24
Ich frage mich, ob die Ansätze in den folgenden Antworten einen echten Vorteil haben, wenn man nur ein List<object>? Sie werden nicht aufhören zu boxen / entpacken, sie werden die Notwendigkeit des Castings nicht beseitigen, und letztendlich erhalten Sie ein MetadataObjekt, das Ihnen nichts über das tatsächliche sagt. DataTypeIch habe nach einer Lösung gesucht, um diese Probleme anzugehen. Wenn Sie eine Schnittstelle / Klasse deklarieren möchten, nur um den implementierenden / abgeleiteten generischen Typ in eine generische Liste aufnehmen zu können, wie unterschiedlich ist das dann von der Verwendung eines List<object>anderen als einer bedeutungslosen Ebene?
Saeb Amini
9
Sowohl die abstrakte Basisklasse als auch die Schnittstelle bieten ein gewisses Maß an Kontrolle, indem sie die Art der Elemente einschränken, die der Liste hinzugefügt werden können. Ich kann auch nicht sehen, wie das Boxen dazu kommt.
0b101010
3
Wenn Sie .NET v4.0 oder höher verwenden, ist Kovarianz natürlich die Lösung. List<Metadata<object>>macht den Trick.
0b101010
2
@ 0b101010 Ich habe das gleiche gedacht, aber leider ist keine Abweichung bei Werttypen zulässig. Da OP eine structEinschränkung hat, funktioniert es hier nicht. Siehe
Nawfal
@ 0b101010, Beide beschränken nur Referenztypen, jeder integrierte Werttyp und jede Struktur können noch hinzugefügt werden. Außerdem haben Sie am Ende eine Liste von MetaDataReferenztypen anstelle Ihrer ursprünglichen Werttypen ohne Informationen (Kompilierungszeit) über den zugrunde liegenden Werttyp jedes Elements, was effektiv "Boxen" bedeutet.
Saeb Amini

Antworten:

195
public abstract class Metadata
{
}

// extend abstract Metadata class
public class Metadata<DataType> : Metadata where DataType : struct
{
    private DataType mDataType;
}
Leppie
quelle
5
Beeindruckend! Ich hätte das wirklich nicht für möglich gehalten! Du bist ein Lebensretter, Mann!
Carl
2
+10 dafür! Ich weiß nicht, warum das kompiliert wird. Genau das, was ich brauchte!
Odys
Ich habe ein ähnliches Problem, aber meine generische Klasse erstreckt sich von einer anderen generischen Klasse, sodass ich Ihre Lösung nicht verwenden kann ... Ideen zur Behebung dieser Situation?
Sheridan
10
Gibt es einen Vorteil dieses Ansatzes gegenüber einem einfachen List<object>? Bitte schauen Sie sich meinen Kommentar unter der Frage von OP an.
Saeb Amini
10
@SaebAmini Eine Liste <Objekt> zeigt einem Entwickler keine Absicht und verhindert auch nicht, dass sich ein Entwickler in den Fuß schießt, indem er fälschlicherweise ein Nicht-MetaData-Objekt zur Liste hinzufügt. Unter Verwendung einer Liste <MetaData> wird verstanden, was die Liste enthalten sollte. Höchstwahrscheinlich verfügt MetaData über einige öffentliche Eigenschaften / Methoden, die in den obigen Beispielen nicht gezeigt wurden. Der Zugriff auf diese über ein Objekt würde eine umständliche Besetzung erfordern.
Buzz
92

Warum nicht nach Leppies Antwort MetaDataeine Schnittstelle erstellen:

public interface IMetaData { }

public class Metadata<DataType> : IMetaData where DataType : struct
{
    private DataType mDataType;
}
bruno conde
quelle
Kann mir jemand sagen, warum dieser Ansatz besser ist?
Lazlo
34
Weil keine gemeinsame Funktionalität gemeinsam genutzt wird - warum dann eine Basisklasse dafür verschwenden? Eine Schnittstelle ist ausreichend
flq
2
Weil Sie Schnittstellen in struct implementieren können.
Damian Leszczyński - Vash
2
Die Klassenvererbung mit virtuellen Methoden ist jedoch ungefähr 1,4-mal schneller als Schnittstellenmethoden. Wenn Sie also nicht generische (virtuelle) MetaData-Methoden / -Eigenschaften in MetaData <DataType> implementieren möchten, wählen Sie eine abstrakte Klasse anstelle einer Schnittstelle, wenn die Leistung ein Problem darstellt. Andernfalls kann die Verwendung einer Schnittstelle flexibler sein.
TamusJRoyce
30

Ich habe auch eine nicht generische Version mit dem newSchlüsselwort verwendet:

public interface IMetadata
{
    Type DataType { get; }

    object Data { get; }
}

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

Die explizite Schnittstellenimplementierung ermöglicht beide DataMitglieder:

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

Sie können eine Version ableiten, die auf Werttypen abzielt:

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

Dies kann auf jede Art von allgemeinen Einschränkungen erweitert werden.

Bryan Watts
quelle
4
+1 nur weil Sie zeigen, wie man es benutzt ( DataTypeund object Dataviel geholfen hat)
Odys
4
Ich scheine zum Beispiel nicht schreiben zu können Deserialize<metadata.DataType>(metadata.Data);. Es sagt mir , dass Symbolmetadaten nicht aufgelöst werden können . Wie rufe ich den Datentyp ab, um ihn für eine generische Methode zu verwenden?
Cœur