C # - Plusieurs types génériques dans une liste

153

Ce n'est probablement pas possible, mais j'ai cette classe:

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

Il y a plus à cela, mais gardons les choses simples. Le type générique (DataType) est limité aux types valeur par l'instruction where. Ce que je veux faire, c'est avoir une liste de ces objets de métadonnées de différents types (DataType). Tel que:

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

Est-ce seulement possible?

Carl
la source
24
Je me demande s'il y a un réel avantage aux approches dans les réponses ci-dessous par rapport à la simple utilisation d'un List<object>? Ils n'arrêteront pas de boxer / déballer, ils ne supprimeront pas le besoin de casting, et finalement, vous obtenez un Metadataobjet qui ne vous dit rien sur le réel DataType, je cherchais une solution pour résoudre ces problèmes. Si vous allez déclarer une interface / classe, juste pour pouvoir mettre le type générique d'implémentation / dérivé dans une liste générique, en quoi cela diffère -t-il de l'utilisation d'un List<object>autre que d'avoir une couche vide de sens?
Saeb Amini
9
La classe de base abstraite et l'interface fournissent un degré de contrôle en limitant le type d'éléments qui peuvent être ajoutés à la liste. Je ne vois pas non plus comment la boxe entre en jeu.
0b101010
3
Bien sûr, si vous utilisez .NET v4.0 ou supérieur, la covariance est la solution. List<Metadata<object>>fait l'affaire.
0b101010
2
@ 0b101010 Je pensais la même chose, mais malheureusement, la variance n'est pas autorisée sur les types valeur. Puisque OP a une structcontrainte, cela ne fonctionne pas ici. Voir
nawfal
@ 0b101010, Les deux limitent uniquement les types de référence, tout type de valeur intégré et toute structure peuvent toujours être ajoutés. En outre, à la fin, vous avez une liste de MetaDatatypes de référence au lieu de vos types de valeur d'origine sans aucune information (au moment de la compilation) sur le type de valeur sous-jacent de chaque élément, c'est effectivement "boxing".
Saeb Amini le

Réponses:

195
public abstract class Metadata
{
}

// extend abstract Metadata class
public class Metadata<DataType> : Metadata where DataType : struct
{
    private DataType mDataType;
}
leppie
la source
5
Hou la la! Je ne pensais vraiment pas que c'était possible! Vous êtes un sauveur de vie, mec!
Carl
2
+10 pour ça! Je ne sais pas pourquoi cela compile .. Exactement ce dont j'avais besoin!
Odys
J'ai un problème similaire, mais ma classe générique s'étend d'une autre classe générique, donc je ne peux pas utiliser votre solution ... des idées sur un correctif pour cette situation?
Sheridan
10
Y a-t-il un avantage à cette approche par rapport à une approche simple List<object>? s'il vous plaît regarder mon commentaire publié sous la question d'OP.
Saeb Amini
11
@SaebAmini Une liste <object> ne montre aucune intention à un développeur, ni n'empêche un développeur de se tirer une balle dans le pied en ajoutant par erreur un objet non-MetaData à la liste. En utilisant une liste <MetaData>, on comprend ce que la liste doit contenir. Les MetaData auront très probablement des propriétés / méthodes publiques qui n'ont pas été montrées dans les exemples ci-dessus. L'accès à ceux-ci via un objet nécessiterait un casting encombrant.
Buzz
92

Suite à la réponse de Leppie, pourquoi ne pas créer MetaDataune interface:

public interface IMetaData { }

public class Metadata<DataType> : IMetaData where DataType : struct
{
    private DataType mDataType;
}
bruno conde
la source
Quelqu'un peut-il me dire pourquoi cette approche est meilleure?
Lazlo
34
Parce qu'aucune fonctionnalité commune n'est partagée - pourquoi gaspiller une classe de base là-dessus alors? Une interface suffit
flq
2
Parce que vous pouvez implémenter des interfaces dans struct.
Damian Leszczyński - Vash
2
Cependant, l'héritage de classe à l'aide de méthodes virtuelles est environ 1,4 fois plus rapide que les méthodes d'interface. Donc, si vous prévoyez d'implémenter des méthodes / propriétés MetaData (virtuelles) non génériques dans MetaData <DataType>, choisissez une classe abstraite plutôt qu'une interface, si les performances sont un problème. Sinon, l'utilisation d'une interface peut être plus flexible.
TamusJRoyce
30

J'ai également utilisé une version non générique, en utilisant le newmot - clé:

public interface IMetadata
{
    Type DataType { get; }

    object Data { get; }
}

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

L'implémentation d'interface explicite est utilisée pour permettre aux deux Datamembres:

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

Vous pouvez dériver une version ciblant les types de valeur:

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

Cela peut être étendu à tout type de contraintes génériques.

Bryan Watts
la source
4
+1 juste parce que vous montrez comment l'utiliser ( DataTypeet que vous avez object Databeaucoup aidé)
Odys
4
Je ne semble pas pouvoir écrire par exemple Deserialize<metadata.DataType>(metadata.Data);. Cela me dit que je ne peux pas résoudre les métadonnées des symboles . Comment récupérer le DataType pour l'utiliser pour une méthode générique?
Cœur