Reputation: 6434
I am getting used to using interfaces, generics and develping them using inheritance in a real envrionment whilst trying use and implement this into a new architecture for one of our upcoming projects and I have a question regarding generics which I am confused about.
This is more of a educational question for myself because I can't understand why .NET doesn't allow this.
If I have a generic class which is (Of T As IA, T2 As A)
then I have the following interfaces and class which implements the base interface
Public Interface IA
Property A As String
End Interface
Public Interface IB
inherits IA
Property B As String
End Interface
Public Class GenericClass(Of T As IA, T2 As A)
'Should be list of IA?
Public list As New List(Of T)
Public Sub Add()
End Sub
End Class
Because I have made T as IA
why in the add method is Dim foo4 As T = New A()
not legal when
Dim foo1 As IA = New A()
Dim foo2 As T
Dim foo3 = Activator.CreateInstance(Of T2)()
Dim x As IA = foo2
Dim y As IA = foo3
list.Add(x)
list.Add(y)
All of the above is? This is becoming a learning curve for me with generics etc. but I am just very confused with why I logically can't do this?
EDIT: Sorry forgot Class A
and error message please see below
Public Class A
Implements IA
Public Property A As String Implements IA.A
End Class
EDIT 2: Error was typed incorrectly
"Value of type class a cannnot be converted to T"
Upvotes: 3
Views: 1344
Reputation: 1258
That's because T
is not the interface IA
itself. It is one implementation of it.
Suppose that you have another class that implements IA
:
Public Class B
Implements IA
Public Property B_A As String Implements IA.A
Public Property OtherProperty as Object
End Class
Then you create a new instance of Generic Class
like this:
Dim genericObject as new GenericClass(Of B, A)
So in this case, T
now is B
, and A
cannot be casted to B
.
In this case instead, replacing the part of your doubt, a code that would make sense for me:
Dim foo4 As IA = New T()
EDIT due to comment
To be able to instantiate T
, it is necessary to declare the New
constraint in the type definition. So the generic class declaration would be:
Public Class GenericClass(Of T As {New, IA}, T2 As A)
Upvotes: 2
Reputation: 81143
It's not exactly clear what you're trying to do, but one problem I notice is that you seem to be assuming that a List<TypeThatImplementsIA>
is somehow interchangeable with a List<IA>
. That is not the case. Imagine that A
were a class of flying birds, and IA
were implemented by creatures that can fly, and someone created a GenericClass<Airplane, BlueJay)
. Even though Airplane
and BlueJay
are both things that can fly, one would not be able to add a BlueJay
to a List<Airplane>
. The one common situation in the Framework where one can use a GenericType<DerivedType>
as a GenericType<BaseType>
is with IEnumerable<T>
. The reason for that is that one can't store T
's into an IEnumerable<T>
--one can only read them out. If one is expecting an IEnumerable<Animal>
and one is given an IEnumerable<MaineCoonCat>
, then every time one expects to read an Animal
, one will read an instance of MainCoonCat
, which inherits from Animal
and may thus substitute for it. This feature of IEnumerable<T>
is called covariance.
There's a limitation to such behavior, though, which stems from the fact that there is a difference between using an interface as a type of storage location (variable, parameter, etc.), versus using it as a constraint. For every non-nullable value type, there are actually two related types within the Runtime. One of them is a real value type, which has no concept of inheritance (but can implement interfaces). The other is a heap-object type which derives from ValueType
(which in turn derives from Object
). Most .net languages will implicitly convert the former type to the latter, and allow code to explicitly convert the latter to the former. Interface-type storage locations can only hold references to heap objects. This is significant because it means that while a struct which implements an interface is convertible to that interface type, that doesn't mean instance of the struct is an instance of that interface type. Covariance works on the premise that every object returned by e.g. an IEnumerable<DerivedType>
may be used directly as an instance of BaseType
without conversion. Such direct substitutability works with inherited class types, and with interfaces that are implemented by class types. It does not work with interfaces implemented by struct types, or with generics that do not have a class
constraint. Adding a class constraint to a generic class type parameter will allow that type parameter to participate in covariance, but may preclude the use of structs as the generic type parameter. Note that unless one has particular reason to expect that an interface will be implemented by structures (as is the case with e.g. IComparable<T>
, in many cases it's unlikely that an interface would be implemented by a structure and thus a class
constraint would be harmless).
Upvotes: 3