Разве ковариация/контравариантность не должна позволять это в С# 4.5?

private Dictionary<Type, List<IDataTransferObject>> dataStore = new Dictionary<Type, List<IDataTransferObject>>();

public void Insert<T>(T dto) where T : IDataTransferObject
{
    if (!dataStore.ContainsKey(typeof(T)))
    {
        dataStore.Add(typeof(T), new List<T>());
    }

    dataStore[typeof(T)].Add(dto);
}

Приведенный выше код дает мне ошибку компиляции в строке dataStore.Add, потому что мне не нравится, когда я пытаюсь присвоить List<T> List<IDataTransferObject>. Поскольку мой метод ограничивает T только IDataTransferObject, не должен ли материал ковариации/контравариантности в .Net 4 разрешать этот код?

Я знаю, что могу изменить его, чтобы сделать новый List<IDataTransferObject>, и он будет работать, но мне любопытно, почему исходный код не работает.


person Dylan Smith    schedule 05.10.2012    source источник
comment
Спешите на помощь.   -  person Sumo    schedule 05.10.2012
comment
Может быть, вы немного запутались в номерах версий? Если вы имеете в виду .NET 4.5 (C# 5), есть новый IReadOnlyList<out T> интерфейс, который является ковариантным в своем T. Обратите внимание, что в .NET ковариантными могут быть только интерфейсы (и типы делегатов), а не классы/структуры. Причина, по которой IReadOnlyList<> может быть сделана ковариантной, заключается в том, что она включает только элементы, которые читают из базового списка (не записывают). Однако IReadOnlyList<> не поможет вам в вашей невозможной проблеме, так что .NET 4.5 здесь, в конце концов, не поможет.   -  person Jeppe Stig Nielsen    schedule 27.11.2012


Ответы (1)


Почти уверен, что List<SubClass> не является ковариантным для List<BaseClass>. IEnumerable<T> может быть, но не List, поскольку вы можете свободно добавить не-T (но все же IDataTransferObjects), который вызовет исключение времени выполнения, поэтому оно будет перехвачено во время компиляции.

Хотя ваш код может быть безопасным во время выполнения (поскольку вы используете ключи по типу), компилятор об этом не знает.

List<Animal> animalList = new List<Animal>();
animalList.Add(new Dog()); //ok!

List<Cat> catList = new List<Cat>();
animalList = catList; //Compiler error: not allowed, but it's what you're trying to do
animalList.Add(new Dog()) //Bad stuff! Trying to add a Dog to a List<Cat>

То, что вы делаете, сработало бы, если бы вы пытались рассматривать его как IEnumerable<IDataTransferObject>, поскольку те, которые не могут быть изменены кодом (если вы не используете его первым, в этот момент он будет проходить/не срабатывать, если вы используете плохой тип). Но List определенно можно изменить кодом времени компиляции.

РЕДАКТИРОВАТЬ: Если вы не возражаете против кастинга и действительно хотите List<T> (поэтому ваш вызывающий код является типобезопасным и не добавляет не-10_ объекты после извлечения), вы можете сделать что-то вроде этого:

private Dictionary<Type, object> dataStore = new Dictionary<Type, object>();

public void Insert<T>(T dto) where T : IDataTransferObject
{
    object data;
    if (!dataStore.TryGetValue(typeof(T), out data))
    {
        var typedData = new List<T>();
        dataStore.Add(typeof(T), typedData);
        typedData.Add(dto);
    }
    else
    {
        ((List<T>)data).Add(dto);
    }
}


//you didn't provide a "getter" in your sample, so here's a basic one
public List<T> Get<T>() where T : IDataTransferObject
{
    object data;
    dataStore.TryGetValue(typeof(T), out data);
    return (List<T>)data;
}

Код вызова выглядит так:

Insert(new PersonDTO());
Insert(new OrderDTO());
Insert(new PersonDTO());

List<PersonDTO> persons = Get<PersonDTO>();
List<OrderDTO> orders = Get<OrderDTO>();

Console.WriteLine(persons.Count); //2
Console.WriteLine(orders.Count); //1

Таким образом, снаружи все использование API безопасно для типов. Вместо того, чтобы orders был List<IDataTransferObject> (что означает, что вы можете добавлять объекты, отличные от OrderDTO), он является строго типизированным и не может смешиваться и сопоставляться.

Конечно, на данный момент нет реальной необходимости ограничиваться IDataTransferObject, но это зависит от вас и вашего API/дизайна/использования.

person Chris Sinclair    schedule 05.10.2012