Question

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

The above code gives me a compile error on the dataStore.Add line because it doesn't like me trying to assign a List<T> to a List<IDataTransferObject>. Since my method restricts T to only IDataTransferObject's shouldn't the covariance/contravariance stuff in .Net 4 allow this code?

I know I can change it to do new List<IDataTransferObject> and it will work, but I'm curious why the original code doesn't work.

Was it helpful?

Solution

Pretty sure a List<SubClass> isn't covariant to List<BaseClass>. IEnumerable<T> maybe, but not List as you can freely add a non-T (but still IDataTransferObjects) which would throw a runtime exception so it's caught at compile time.

While your code might be safe at runtime (as you use keys by type), the compiler doesn't know this.

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>

What you're doing would work if you were trying to treat it as IEnumerable<IDataTransferObject> as those cannot by modified by code (unless you cast it first at which point it would pass/fail if you use a bad type). But List can definitely be altered by compile-time code.

EDIT: If you don't mind casting, and really want a List<T> (so your calling code is typesafe and not adding non-T objects once retrieved) you might do something like this:

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

Calling code is like:

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

So from the outside, all API usage is typesafe. Instead of orders being a List<IDataTransferObject> (which means you can add non-OrderDTO objects), it's strongly typed and cannot be mixed and matched.

Of course at this point, there's no real need to constrain to IDataTransferObject, but that's up to you and your API/design/usage.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top