C # - Кілька загальних типів в одному списку


153

Це, мабуть, неможливо, але у мене є такий клас:

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

Тут є більше, але давайте просто. Загальний тип (DataType) обмежений типами значень за допомогою оператора where. Що я хочу зробити - це перелік цих об’єктів метаданих різного типу (DataType). Як от:

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

Це навіть можливо?


24
Цікаво, чи є реальна користь для підходів у наведених нижче відповідях порівняно з просто використанням List<object>? Вони не зупинятимуть бокс / розблокування, не знімуть потреби в кастингу, і, зрештою, ви отримуєте Metadataоб'єкт, який нічого не розповість про фактичне DataType, я шукав рішення для вирішення цих проблем. Якщо ви збираєтесь оголосити інтерфейс / клас, просто заради того, щоб мати змогу поставити реалізований / похідний загальний тип у загальний список, наскільки це відрізняється від використання List<object>іншого, ніж мати безглуздий шар?
Саеб Аміні

9
І абстрактний базовий клас, і інтерфейс забезпечують ступінь контролю, обмежуючи тип елементів, які можна додати до списку. Я також не бачу, як бокс входить у це.
0b101010

3
Звичайно, якщо ви використовуєте .NET v4.0 або вище, коваріація - це рішення. List<Metadata<object>>робить трюк.
0b101010

2
@ 0b101010 Я думав так само, але, на жаль, для типів значень відхилення заборонено. Оскільки в ОП є structобмеження, воно тут не працює. Дивіться
nawfal

@ 0b101010, Обидва обмежують лише типи посилань, будь-який вбудований тип значення та будь-яку структуру. Також, врешті-решт, у вас є список MetaDataтипів посилань замість вихідних типів значень, що не мають (час компіляції) інформації про тип базового значення кожного елемента, це ефективно "бокс".
Saeb Amini

Відповіді:


195
public abstract class Metadata
{
}

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

5
Оце Так! Я дійсно не думав, що це можливо! Ти рятівник життя, чоловіче!
Карл

2
+10 для цього! Я не знаю, навіщо це складається. Саме те, що мені потрібно було!
Одіс

У мене є подібна проблема, але мій загальний клас поширюється на інший загальний клас, тому я не можу використовувати ваше рішення ... будь-які ідеї щодо виправлення цієї ситуації?
Шерідан

10
Чи є користь від цього підходу порівняно з простим List<object>? будь ласка, подивіться на мій коментар, розміщений під питанням ОП.
Саеб Аміні

11
@SaebAmini Список <object> не показує розробника жодних намірів, а також не заважає розробнику стріляти в ногу, помилково додаючи до списку якийсь неметадданий об'єкт. За допомогою списку <MetaData> розуміється, що список повинен містити. Швидше за все, метадані матимуть деякі публічні властивості / методи, які не були показані у наведених вище прикладах. Доступ до них через об'єкт потребує громіздкого акторського складу.
Buzz

92

Після відповіді леппі, чому б не зробити MetaDataінтерфейс:

public interface IMetaData { }

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

Може хтось скаже мені, чому такий підхід кращий?
Лазло

34
Оскільки загальна функціональність не поділяється - навіщо тоді витрачати базовий клас на це? Інтерфейсу достатньо
flq

2
Тому що ви можете реалізувати інтерфейси в структура.
Damian Leszczyński - Vash

2
Успадкування класів за допомогою віртуальних методів приблизно в 1,4 рази швидше, ніж інтерфейсні методи. Тож якщо ви плануєте впроваджувати будь-які не загальні методи / властивості метаданих (віртуальних) у MetaData <DataType>, виберіть абстрактний клас, а не інтерфейс, якщо продуктивність викликає занепокоєння. В іншому випадку використання інтерфейсу може бути більш гнучким.
TamusJRoyce

30

Я також використовував не загальну версію, використовуючи newключове слово:

public interface IMetadata
{
    Type DataType { get; }

    object Data { get; }
}

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

Явна реалізація інтерфейсу використовується для забезпечення обох Dataчленів:

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

Ви можете отримати типи значень націлювання на версії:

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

Це може поширюватися на будь-які загальні обмеження.


4
+1 лише тому, що ви показуєте, як ним користуватися ( DataTypeі дуже object Dataдопомогли)
Одіс

4
Я, здається, не можу написати, наприклад Deserialize<metadata.DataType>(metadata.Data);. Це говорить мені, що не можна вирішити метадані символу . Як отримати DataType, щоб використовувати його для загального методу?
Cœur
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.