I'm targeting .NET 3.5. Let's say I have a class, Bob, which is an abstract base class for SubBob.
I can declare this:
Bob b = new SubBob();
But I can't do this:
// compliation error - can't convert
BindingList<Bob> myList = new BindingList<SubBob>();
My guess is that BindingList doesn't want you to do this because it has to know that the type that's on the right hand side has the same memory layout as the left hand side. SubBob may have a larger size than a Bob.
Is there a way I can do the implicit conversion, or is a cast required?
By instantiating BindingList<SubBob>
you confine it to work with SubBob
and more specific types (e.g. SubSubBob
).
If you want Bob
to fit there as well, declare myList
as a list of the least specific type you want to support:
BindingList<Bob> myList = new BindingList<Bob>();
(or, more conveniently,)
var myList = new BindingList<Bob>();
It is not about memory (BindingList
would only hold a reference to the object, and all references are of the same size), rather it is about logical inconsistency you would introduce.
If such code was possible, you would be able to arbitrarily break type restrictions:
BindingList<Animal> myList = new BindingList<Cat>();
myList.Add(new Dog()); // bang!
myList
is a list of Cat
s, how would you expect it to handle a Dog
?
Compiler wouldn't know there is a problem and would happily compile your code. What should happen when this code runs? An exception? But generics were introduced exactly to solve type safety problem.
It's correct that in .NET 4.0, generic covariance and contravariance were added for delegates and interfaces (not for classes). For example, IEnumerable<out T>
is covariant that means you can assign it to variable of any type less derived than T
:
IEnumerable<Cat> cats = new List<Cat> { new Cat("Hudson"), new Cat("Crookshanks") };
IEnumerable<Animal> animals = cats; // sequence of cats is sequence of animals
But this is only possible because IEnumerable<out T>
guarantees it only returns T
(keyword out
) and never accepts it. If it accepted T
as parameter, it would open the door to the problem described above. For this reason, ICollection
is not covariant.
In a similar fashion, some interfaces guarantee they only accept T
(keyword in
) and never return it. Such interfaces are called contravariant and allow assignment to a variable with more specific T
:
IComparer<Animal> animalComparer = // ...
IComparer<Dog> dogComparer = animalComparer; // comparer of animals is comparer of dogs
Regarding your case specifically:
You are dealing with contravariance here and a class can only support covariance OR contravariance. IEnumerable<T>
supports covariance.
Its signature is IEnumerable<out T>
, so it supports inherited classes of the class in T
.
In general:
Unfortunately this is not available in .NET 3.5.
It was introduced in .NET 4 as Covariance and Contravariance.
There is a FAQ on the subject at MSDN. Here's an excerpt:
// Assignment compatibility.
string str = "test";
// An object of a more derived type is assigned to an object of a less derived type.
object obj = str;
// Covariance.
IEnumerable<string> strings = new List<string>();
// Contravariance.
// Assume that I have this method:
// static void SetObject(object o) { }
Action<object> actObject = SetObject;
Is there a way I can do the implicit conversion, or is a cast required?
The following should let any class that inherits Bob to be added to the list as a Bob.
BindingList<Bob> myList = new BindingList<Bob>();
What exactly are you trying to do?
The reason your code doesn't work is that generics don't inherit the hierarchy of their arguments: BindingList<SubBob>
does not derive from BindingList<Bob>
. It has nothing to do with their memory layouts.
A BindingList<Bob>
can already contain SubBob
objects (and anything else deriving from Bob
), so there might not actually be a need for a BindingList<SubBob>
. If for some reason there is a reason you need both, your only "easy" option may be to use the non-generic IBindingList
interface.
.NET 4 supports interface covariance which allows downconverting read-only generic interfaces into the same interface with a base class, so it may or may not be helpful in your situation... that is, if you weren't stuck with 3.5:
IEnumerable<Bob> myList = new BindingList<SubBob>();
If you're stuck with .NET 3.5 and cannot change your data access layer to provide something more convenient, you can use a rather dirty conversion:
BindingList<SubBob> subBobs = new BindingList<SubBob>;
BindingList<Bob> bobs = new BindingList(subBobs.Cast<Bob>().ToList())
Apart from mediocre design, this introduces the cost of reconstructing the list from IEnumerable<>
in ToList()
. BindingList<>
constructor on the other hand only wraps the list.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With