Reputation: 22245
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.
Upvotes: 2
Views: 703
Reputation: 23208
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.
Upvotes: 6