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>другого, отличного от бессмысленного слоя?
Saeb Amini
9
Как абстрактный базовый класс, так и интерфейс обеспечивают степень контроля, ограничивая тип элементов, которые могут быть добавлены в список. Я также не могу понять, как бокс вступает в это.
0b101010
3
Конечно, если вы используете .NET v4.0 или выше, то ковариация является решением. List<Metadata<object>>делает трюк.
0b101010
2
@ 0b101010 Я думал о том же, но, к сожалению, отклонение недопустимо для типов значений. Поскольку у OP есть structограничение, оно здесь не работает. Смотрите
nawfal
@ 0b101010, оба могут ограничивать только ссылочные типы, любой встроенный тип значения и любая структура все еще могут быть добавлены. Кроме того, в конце у вас есть список MetaDataссылочных типов вместо ваших исходных типов значений без информации о времени компиляции (времени компиляции), что фактически является «упаковкой».
Саиб Амини,

Ответы:

195
public abstract class Metadata
{
}

// extend abstract Metadata class
public class Metadata<DataType> : Metadata where DataType : struct
{
    private DataType mDataType;
}
leppie
источник
5
Вот Это Да! Я действительно не думал, что это было возможно! Ты спасатель, чувак!
Карл
2
+10 за это! Я не знаю, почему это компилируется .. Именно то, что мне нужно!
Одис
У меня похожая проблема, но мой универсальный класс происходит от другого универсального класса, поэтому я не могу использовать ваше решение ... какие-либо идеи по исправлению ситуации?
Шеридан
10
Есть ли польза от этого подхода по сравнению с простым List<object>? пожалуйста, посмотрите на мой комментарий, опубликованный под вопросом ОП.
Saeb Amini
11
@SaebAmini List <объект> не показывает никакого намерения разработчику, и при этом он не мешает разработчику выстрелить себе в ногу, ошибочно добавив некоторый объект не-MetaData в список. Под списком <MetaData> понимается, что должен содержать список. Скорее всего, MetaData будет иметь некоторые открытые свойства / методы, которые не были показаны в приведенных выше примерах. Доступ к ним через объект потребовал бы громоздкого приведения.
Buzz
92

После ответа Леппи, почему бы не сделать MetaDataинтерфейс:

public interface IMetaData { }

public class Metadata<DataType> : IMetaData where DataType : struct
{
    private DataType mDataType;
}
Бруно Конде
источник
Может кто-нибудь сказать мне, почему этот подход лучше?
Лазло
34
Потому что нет общей функциональности - зачем тратить на это базовый класс? Интерфейс достаточно
FLQ
2
Потому что вы можете реализовать интерфейсы в структуре.
Дамиан Лещинский - Ваш
2
Наследование классов с использованием виртуальных методов, однако, примерно в 1,4 раза быстрее, чем методы интерфейса. Поэтому, если вы планируете реализовать какие-либо неуниверсальные (виртуальные) методы / свойства MetaData в 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помогли)
Odys
4
Кажется, я не могу писать, например Deserialize<metadata.DataType>(metadata.Data);. Это говорит мне, что не может разрешить метаданные символа . Как получить DataType, чтобы использовать его для универсального метода?
Cœur