Should covariance / contravariance allow this in C # 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); } 

The above code gives me a compilation error in the string dataStore.Add because I don't like assigning List<T> a List<IDataTransferObject> . Since my method restricts T to only IDataTransferObject, should covariance / contravariant material in .Net 4 not allow this code?

I know that I can change it to make a new List<IDataTransferObject> and it will work, but I'm curious why the source code is not working.

+2
c # covariance contravariance
source share
1 answer

Pretty precisely, a List<SubClass> not covariant to a List<BaseClass> . IEnumerable<T> may be, but not List, since you can freely add not T (but still IDataTransferObjects ), which would throw an exception at runtime to IDataTransferObjects it at compile time.

Although your code may be safe at runtime (since you use keys by type), the compiler does not 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 what you're trying to do animalList.Add(new Dog()) //Bad stuff! Trying to add a Dog to a List<Cat> 

What you do will work if you try to treat it as an IEnumerable<IDataTransferObject> , since they cannot be changed using the code (unless you say it first, at what point it will go through / crash if you use bad type of). But List can definitely be changed by compile-time code.

EDIT: If you don't mind casting and really want a List<T> (so your calling code is typical and doesn't add non- T objects after extraction), you can 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 a basic one public List<T> Get<T>() where T : IDataTransferObject { object data; dataStore.TryGetValue(typeof(T), out data); return (List<T>)data; } 

The call code is as follows:

 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 

Thus, from outside, all use of the API is typical. Instead of orders is List<IDataTransferObject> (which means that you can add objects not OrderDTO ), it is strongly typed and cannot be mixed and matched.

Of course, at the moment there is no real need to restrict IDataTransferObject , but it depends on you and your API / design / use.

+6
source share

All Articles