Generic Constraints Question

  • Thread starter Thread starter SyraJM
  • Start date Start date
S

SyraJM

I’m running into a problem using Generics in my code. I’ve created a
contrived example below to illustrate my design goals and my confusion.

I declare an Interface called IAnimal. I create several classes that
implement this interface. Let’s call these classes Dog, Horse, Fish.

I create a List of Animals based on the Interface like this:

Dim animalListing As New List (Of IAnimal)

I then create instances of each of my classes and load these instances into
the list. Now I have a Dog, Horse and Fish in animalListing.

I’ve created a generic method as shown below.

Private Sub FeedAnimal(Of T As IAnimal)()

I want to iterate my list and call the method for each object instance –
basing the call on the type of the current object. I thought the code would
look like this:

For Each a As IAnimal In animalListing
FeedAnimal (Of a)
Next

However, this doesn’t work. Why? All I’ve had success with is adding some
sort of logic switch:

For Each a As IAnimal in animalListing
If TypeOf (a) Is Dog Then
FeedAnimal (Of Dog)()
ElseIf TypeOf (a) Is Horse Then
FeedAnimal (Of Horse)()
End If
Next

I’m obviously missing something obvious because my Generic code isn’t
“genericâ€.
 
SyraJM said:
I’m running into a problem using Generics in my code. I’ve created a
contrived example below to illustrate my design goals and my
confusion.

I declare an Interface called IAnimal. I create several classes that
implement this interface. Let’s call these classes Dog, Horse, Fish.

I create a List of Animals based on the Interface like this:

Dim animalListing As New List (Of IAnimal)

I then create instances of each of my classes and load these
instances into the list. Now I have a Dog, Horse and Fish in
animalListing.

I’ve created a generic method as shown below.

Private Sub FeedAnimal(Of T As IAnimal)()

I want to iterate my list and call the method for each object
instance – basing the call on the type of the current object. I
thought the code would look like this:

For Each a As IAnimal In animalListing
FeedAnimal (Of a)
Next

However, this doesn’t work. Why? All I’ve had success with is
adding some sort of logic switch:

For Each a As IAnimal in animalListing
If TypeOf (a) Is Dog Then
FeedAnimal (Of Dog)()
ElseIf TypeOf (a) Is Horse Then
FeedAnimal (Of Horse)()
End If
Next

I’m obviously missing something obvious because my Generic code isn’t
“genericâ€.


The 'a' in "FeedAnimal (Of a)" must be a type name, not a variable name.
Otherwise the compiler can't compile the code.

This call works:

FeedAnimal(Of IAnimal)()

_But_, where do you intend to pass the object? You had to add a parameter:

Private Sub FeedAnimal(Of T As IAnimal)(Byval Animal As T)

So the call is:

FeedAnimal(Of IAnimal)(a)



However, why don't you simply declare it as

Private Sub FeedAnimal(Byval Animal As IAnimal)

?

With Option Infer On, you can write

For Each a In animalListing
FeedAnimal(a)
Next


Armin
 
Hi Armin,
Thanks for your reply. As I mentioned, this is a contrived example. That's
why I can't just pass a parameter to a non-generic method as you suggested
here:
However, why don't you simply declare it as

Private Sub FeedAnimal(Byval Animal As IAnimal)

What I'm really trying to do is deal in types. In my production code, I
have a series of generic classes that are constrained in a similar way to my
sample method. If I employ logic like shown in my If...Then... block,
everything downstream works just fine. However, those few lines seem
inelegant - particularly with what I can do in my other generic routines.

I was really trying to find out if there is a way to realize the type of the
object instance at run-time and then pass that type into my generic method.

I was trying stuff like this:

For Each a As IAnimal In animalListing
Dim t as Type = a.GetType.FullName 'as well as other attempts
FeedAnimal (Of t)()
Next

But none of these approaches is working because I have object instances
where I need a Type. My main goal is to iterate a group of objects that all
implement a common interface and pass them to a generic method that is
constrained for that same interface. But I can't seem to get at the right
syntax.

During debug, I can mouse-over the variable a during each iteration of the
loop and see the correct className (Dog, Horse, Fish). Now I'm just looking
for how I can pass the instance's type to my generic method.

I'm going to look over my design again and see if there might be an
alternative approach - maybe in the way I have my other generic classes set
up.
 
SyraJM said:
I’m running into a problem using Generics in my code.  I’ve createda
contrived example below to illustrate my design goals and my confusion.

I declare an Interface called IAnimal.  I create several classes that
implement this interface.  Let’s call these classes Dog, Horse, Fish.

I create a List of Animals based on the Interface like this:

Dim animalListing As New List (Of IAnimal)

I then create instances of each of my classes and load these instances into
the list.  Now I have a Dog, Horse and Fish in animalListing.

I’ve created a generic method as shown below.

Private Sub FeedAnimal(Of T As IAnimal)()

It seems to me that the method should be

Sub FeedAnimal(Of T As Animal)(A As T)
...

This would allow you to have something like:

Dim D As New Dog
FeedAnimal(D)

But then, again, a regular method with an IAnimal parameter would
allow the same thing:

Sub FeedAnimal(A As IAnimal)
'...
End Sub

Dim D As New Dog
FeedAnimal(D)

What a generic method might help you would be to allow you to pass
strongly type composite classes as parameters.
For example:

Sub FeedAnimals(Of T As IAnimal)(List As IEnumerable(Of T))
For Each A As T In List
'...
Next
End Sub

Now, supposed you had a list of dogs, you could pass it to
FeeedAnimals without any problems:

Dim Dogs As New List(Of Dog)
'...
FeedAnimals(Dogs)

The advantage isn't obvious, but suppose that instead of a generic
method, you had one that required an IEnumerable(Of IAnimal) instead:

Sub FeedIAnimals(List As IEnumerable(Of IAnimal))
For Each A As IAnimal In List
'...
Next
End Sub

Dim Dogs As New List(Of Dog)
'...
FeedIAnimals(Dogs) '<<--- Ops, syntax error!
I want to iterate my list and call the method for each object instance –
basing the call on the type of the current object.  I thought the code would
look like this:

For Each a As IAnimal In animalListing
        FeedAnimal (Of a)
Next
<snip>

A generic parameter must be a type, not an object instance. FeedAnimal
(Of A) is a syntax error.

HTH.

Regards,

Branco.
 
SyraJM said:
Hi Armin,
Thanks for your reply. As I mentioned, this is a contrived example.
That's why I can't just pass a parameter to a non-generic method as
you suggested here:


What I'm really trying to do is deal in types. In my production
code, I have a series of generic classes that are constrained in a
similar way to my sample method. If I employ logic like shown in my
If...Then... block, everything downstream works just fine. However,
those few lines seem inelegant - particularly with what I can do in
my other generic routines.

I was really trying to find out if there is a way to realize the type
of the object instance at run-time and then pass that type into my
generic method.

I was trying stuff like this:

For Each a As IAnimal In animalListing
Dim t as Type = a.GetType.FullName 'as well as other attempts
FeedAnimal (Of t)()
Next

Why not

FeedAnimal (Of IAnimal)()

?
But none of these approaches is working because I have object
instances where I need a Type. My main goal is to iterate a group of
objects that all implement a common interface and pass them to a
generic method that is constrained for that same interface. But I
can't seem to get at the right syntax.

But I've shown you the right syntax:

FeedAnimal(Of IAnimal)()

In addition you must pass each object to the generic function. Otherwise the
loop does not make sense at all. Therefore you must write

FeedAnimal(Of IAnimal)(a)

During debug, I can mouse-over the variable a during each iteration
of the loop and see the correct className (Dog, Horse, Fish). Now
I'm just looking for how I can pass the instance's type to my generic
method.

Generics are resolved by the compiler (at compile time). If you're working
with Type objects - and I don't know why - you're not making use of
generics. The information, I mean the instance type, that you get during
debugging isn't available at compile time.
I'm going to look over my design again and see if there might be an
alternative approach - maybe in the way I have my other generic
classes set up.

I don't understand why the above is not the solution the problem.


Armin
 
But then, again, a regular method with an IAnimal parameter would
allow the same thing:

Sub FeedAnimal(A As IAnimal)
'...
End Sub

Dim D As New Dog
FeedAnimal(D)

The ironic part of this post is that earlier today, I was looking at code
like this and trying to find a better solution.

Here is a snippet of my current production method, where I simply pass an
object to a non-generic method:

Private Sub LoadPanelServer(ByVal oE As IEditObject)
Try
If TypeOf (oE) Is APPOCategory Then panelServer = DirectCast(New
PanelServer(Of APPOCategory), IPanelServer(Of IEditObject))
ElseIf TypeOf (oE) Is TaxExemptCategory Then panelServer = DirectCast(New
PanelServer(Of TaxExemptCategory), IPanelServer(Of IEditObject))
ElseIf TypeOf (oE) Is BEVLevel Then panelServer = DirectCast(New
PanelServer(Of BEVLevel), IPanelServer(Of IEditObject))
etc....

It looks ugly here but you can see the pattern that develops. Here is the
supporting info:

I have a Class called PanelServer as declared below:

Public Class PanelServer(Of T As {IEditObject, New})
Implements IPanelServer(Of T)

There is an Interface called IPanelServer declared below:

Public Interface IPanelServer(Of T As IEditObject)

Finally, the local variable being set in my code snippet showing the
If...ElseIf... block called panelServer is declared below:

Private WithEvents panelServer As IPanelServer(Of IEditObject)

In the If...ElseIf... block, each of the classes shown (APPOCategory,
BEVLevel, etc) all implement the IEditObject interface.

If this post just obfuscates the issue, I'm sorry. I just thought I'd throw
up the real-life approach instead of just the contrived example.

One other item that I don't understand is why I'm forced to box my classes:
Since APPOCategory implements the IEditObject interface I don't understand
why the compiler is requiring this code

panelServer = DirectCast(New PanelServer(Of APPOCategory), IPanelServer(Of
IEditObject))

instead of this code

panelServer = New PanelServer(Of APPOCategory)
 
SyraJM said:
Hi Armin,
Thanks for your reply.  As I mentioned, this is a contrived example.  That's
why I can't just pass a parameter to a non-generic method as you suggested
here:



What I'm really trying to do is deal in types.  In my production code, I
have a series of generic classes that are constrained in a similar way tomy
sample method.  If I employ logic like shown in my If...Then... block,
everything downstream works just fine.  However, those few lines seem
inelegant - particularly with what I can do in my other generic routines.
<snip>

Generics provide ways of applying common behavior to dissimilar
classes, but it seems you are trying to apply distinct behavior to
similar classes (which is a typical task for polymorphism, instead).

In the case of your IAnimal interface, maybe you could have a
GetFeeder As IFeeder method so each specific class would provide it's
own feeder, without you having to resort to casting:

Class Dog
Implements IAnimal
...
Function GetFeeder As IFeeder _
Implements IAnimal.GetFeeder
Return New DogFeeder(Me)
End Function
End Class

Class Horse
Implements IAnimal
...
Function GetFeeder As IFeeder _
Implements IAnimal.GetFeeder
Return New HorseFeeder(Me)
End Function
End Class

For Each A As IAnimal In Animals
A.GetFeeder().FeedYourAnimal
Next

HTH.

Regards,

Branco.
 
SyraJM said:
One other item that I don't understand is why I'm forced to box my
classes: Since APPOCategory implements the IEditObject interface I
don't understand why the compiler is requiring this code

An APPOCategory object implements IEditobject but it does not implement
IPanelServer(Of IEditObject). (Is this right? That's what I take from your
posting.) Therefore the assignment does not work, neither with nor without
casting. With casting it will fail at runtime.

Is this the equivalent code?

Public Class Form1
Interface IEditObject
End Interface

Public Interface IPanelServer(Of T As IEditObject)
End Interface

Public Class PanelServer(Of T As {IEditObject, New})
Implements IPanelServer(Of T)
End Class

Class APPOCategory
Implements IEditObject

End Class

Private _panelServer As IPanelServer(Of IEditObject)

Private Sub Form1_Load( _
ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles MyBase.Load

'_panelServer = New PanelServer(Of APPOCategory) '<<<< compile error

_panelServer = DirectCast( _
New PanelServer(Of APPOCategory), IPanelServer(Of IEditObject) _
) '<<<< InvalidCastException.

End Sub

End Class


Armin
 
SyraJM wrote:
Here is a snippet of my current production method, where I simply pass an
object to a non-generic method:

Private Sub LoadPanelServer(ByVal oE As IEditObject)
Try
If TypeOf (oE) Is APPOCategory Then panelServer = DirectCast(New
PanelServer(Of APPOCategory), IPanelServer(Of IEditObject))
ElseIf TypeOf (oE) Is TaxExemptCategory Then panelServer = DirectCast(New
PanelServer(Of TaxExemptCategory), IPanelServer(Of IEditObject))
ElseIf TypeOf (oE) Is BEVLevel Then panelServer = DirectCast(New
PanelServer(Of BEVLevel), IPanelServer(Of IEditObject))
etc....
I have a Class called PanelServer as declared below:

Public Class PanelServer(Of T As {IEditObject, New})
Implements IPanelServer(Of T)

There is an Interface called IPanelServer declared below:

Public Interface IPanelServer(Of T As IEditObject)

Finally, the local variable being set in my code snippet showing the
If...ElseIf... block called panelServer is declared below:

Private WithEvents panelServer As IPanelServer(Of IEditObject)

In the If...ElseIf... block, each of the classes shown (APPOCategory,
BEVLevel, etc) all implement the IEditObject interface.
One other item that I don't understand is why I'm forced to box my classes:  
Since APPOCategory implements the IEditObject interface I don't understand
why the compiler is requiring this code

panelServer = DirectCast(New PanelServer(Of APPOCategory), IPanelServer(Of
IEditObject))

instead of this code

panelServer = New PanelServer(Of APPOCategory)

The issue is just the effect of absent support for "generic
covariance" in VB.

Generic Covariance states that if you have reference types A and B
where:

Dim Obj As A = New B

(i.e., class B implements A in some way), then a covariant generic
interface on B is also assignment compatible to the same interface on
A:

Dim Obj As SomeClass(Of A) = New SomeClass(Of B)

Unfortunately VB lacks the notation to indicate that IPanelServer(Of
T) can be covariant, thus the need to *force*feed*the*damn*compiler*
with the casts =))

Therefore, unfortunatelly, I don't see a way out of the cast (not
until VB 2010, where covariance will be supported, it seems).

As for the "ugly" type test, it has an additional problem: if you add
another class that can also have a PanelServer, you must go back to
the LoadPanelServer method and add support to the new class.
Unnecessary to say that you (or someone that comes after you, which I
hope it doesn't happen anytime soon) will forget to do that. A
possible approach *could* be to have another interface (inheriting
from IEditableObject) with a method to return the appropriate panel
server (you'd still have the cast, though):

Interface IPanelItem
Inherits IEditableObject
Function NewPanelServer As IPanelServer(Of IEditableObject)
End Interface

Class APPOCategory
Implements IPanelItem
'...
Function NewPanelServer As IPanelServer(Of IEditableObject) _
Implements IPanelItem.NewPanelServer
Return DirectCast( _
New PanelServer(Of APPOCategory), _
IPanelServer(Of IEditableObject))
End Function
Enmd Class

Private Sub LoadPanelServer(ByVal oE As IpanelItem)
PanelServer = oE.NewPanelServer
End Sub

HTH.

Regards,

Branco
 
Armin Zingler said:
An APPOCategory object implements IEditobject but it does not implement
IPanelServer(Of IEditObject). (Is this right? That's what I take from your
posting.) Therefore the assignment does not work, neither with nor without
casting. With casting it will fail at runtime.

Armin,
You're absolutely correct - the cast does fail at run-time. I didn't
realize that so thank you for bringing it up.

Reading Branco's comments any my reply below, I think I'll try his
suggestion of inheriting interfaces so that I workaround the covariance
issue. Thanks very much for looking at this with us. My main goal is to
make my code as general as possible and I think I'll be able to get back to a
method signature like this (similar to my original code except for the
parameter type):

Private Sub LoadPanelServer(ByVal oE As IpanelItem)
PanelServer = oE.NewPanelServer
End Sub

Cheers!
 
Branco said:
The issue is just the effect of absent support for "generic
covariance" in VB.

A possible approach *could* be to have another interface (inheriting
from IEditableObject) with a method to return the appropriate panel
server (you'd still have the cast, though):
....
Private Sub LoadPanelServer(ByVal oE As IpanelItem)
PanelServer = oE.NewPanelServer
End Sub

Branco,
Thanks very much for your help with this issue. I hadn't come around to
realizing that this was a covariance issue. The approach to inheriting an
interface might just be the way to solve this issue. I'm not opposed to
doing the casting as you suggest:
Function NewPanelServer As IPanelServer(Of IEditableObject) _
Implements IPanelItem.NewPanelServer
Return DirectCast( _
New PanelServer(Of APPOCategory), _
IPanelServer(Of IEditableObject))
End Function
End Class

I think this is an elegant solution for two reasons:
1) even though I'm forced to perform a cast, that code is encapsulated
within each individual class - not in the general code for the consumer
2) the consumer class is now generalized away from my horrible
If...ElseIf... logic with the simple method signature
Private Sub LoadPanelServer(ByVal oE As IpanelItem)
PanelServer = oE.NewPanelServer
End Sub

Turns out that I don't need a generic method signature after all! I should
know if this works in the next hour or so of coding. In any case, this puts
me well past where I was stuck yesterday and I really appreciate your time
offering suggestions.

Cheers!
 
Back
Top