Testing the TypeOf a Generic?

Mike_R

Junior Contributor
Joined
Oct 20, 2003
Messages
316
Location
NYC
Hi guys,

This is very theoretical, and not very practical, but I was wondering what other people's thoughts were on the following:

Visual Basic:
Shared Sub RunIsTypeOf()
     Dim obj As Object
     Dim isSameType As Boolean

     obj = New Employee ' <-- Which Inherits from the 'Person' class.
     isSameType = TypeOf obj Is Person
     MessageBox.Show(isSameType.ToString) ' <-- Returns True

     obj = New Generic.List(Of Employee)

     isSameType = TypeOf obj Is Generic.List(Of Employee)
     MessageBox.Show(isSameType.ToString) ' <-- Returns True

     isSameType = TypeOf obj Is Generic.List(Of Person)
     MessageBox.Show(isSameType.ToString) ' <-- Returns False!
 End Sub

 Class Person

 End Class

 Class Employee
     Inherits Person
 End Class
Note that the last line returns 'False' which makes sense, but, well, in some sense you might like it to return 'True'. As a practical matter, so long as the list is not empty, one could pop out one element and then check the type on that, so, really, I don't see any *real* problem with this scenario...

But I was wondering what anyone else's thoughts were on this?

Mike
 
That is interesting becuase:
Visual Basic:
        Dim employees(5) As Employee
        isSameType = TypeOf employees Is Employee()
        MessageBox.Show(isSameType.ToString) ' <-- Returns True

        isSameType = TypeOf employees Is Person()
        MessageBox.Show(isSameType.ToString) ' <-- Returns True
It seems like the behavior of the generic list is not consistent. I would have expected true in the last case of your example, simliarly to how an Employee array is also a Person array. But, I have only recently started working with generics (and they are sweet so far) so there might be a completely valid explination for this.
 
Ah yes! Very cool... And this actually opens a door to something else...

If you think about what you've just shown, it suggests that arrays exhibit a form of multiple inherintance, which is not supposed to be allowed by the CLR. That is, arrays inherit from the 'System.Array' class, but they also seem to inherit from the type to which they are declared. (You have proven the latter.)

I don't know whether to think of arrays as "inherently generic" or "exhibiting mutiple inheritance", but it would seem to be closer to the latter. From what I understand, multiple inheritance is supposed to be a bear to implement on the back end, which is why we don't have it in .NET... But I wonder if for Array's that they found it necessary and decided to implement multiple inheritance for array's only behind-the-scenes?

Or perhapse array's are actually generic behind-the-scenes and they have some *extra* check or the like to allow type-checking to make them appear to exhibit multiple inheritance?

Pure speculation on my part, for I do not have a clue here, but maybe some of the guru's around here can shed some light....
 
Also if you do
Visual Basic:
obj = New Generic.List(Of Person)

it is true for a TypeOf obj Is Generic.List(Of Person) check and false for a TypeOf obj Is Generic.List(Of Employee)
 
Well, you'd expect that in this case, right?

('Person' is the base class in this mini-example, and 'Employee' is the derived class.)
 
This has been bugging me a little because it seems so peculiar so I've done a little research. Skimming through the C# language spec it seems that the whole point of generics is to remove the very behavior that we are used to seeing. Reducing the costs associated with boxing/unboxing objects seems to supersede the convenience of inheritance in the case of generics. At least that's what the language in the spec seems to imply, though the example under disscussion is never explicitly addressed.

In a pre-generics world I would have expected a Generic.List(Of Employee) to be the types:
- Generic.List(Of Employee)
- Generic.List(Of Person) because an employee is a person
- Generic.List(Of Object) because everything inherits from object.

But when you think about the problem that generics are meant to solve, specifically
1. Finding compile time errors before they become runtime errors (preventing bad cast exceptions) and
2. Decreasing the performance impact of boxing/unboxing specific types to and from Object (by adding an Employee to a regular ol' ArrayList for example)
then really it doesn't make sense to suddenly be able to start casting objects from a generic list.? By reason two, you'd be allowing the very thing you are trying to prevent. And by reason 1, you've already shot yourself in the foot by declaring your generic list as an object.

So, for the kind of flexibility you might want while still dealing with strongly typed lists, in an example similar to this where you have a well understood base class with multiple inheriting classes, it looks like you have to implement a strongly typed list the "old way", by inheriting from CollecitonBase.

At the risk of making this post way too long, I'm going to propose a new way of thinking for generics.

One of the things you can do with generics is enforce constraints on types that a generic class accepts. Instead of casting an employee to a person (and taking the casting hit) you declare your generic list so that it only accepts objects that implement the person interface or person class.

I'm going to flesh the example out a little bit further to show the way I think generics are intended to be used to get what we want.

Visual Basic:
Class Person
    Public ReadOnly Property name() As String
        Get
            Return "Generic Joe"
        End Get
    End Property
End Class
'
Class Employee
    Inherits Person
    Public ReadOnly Property Job() As String
        Get
            Return "Make things difficult when learning generics."
        End Get
    End Property
End Class
'
Class PersonList(Of T As Person)
    Inherits List(Of T)
End Class

When we do the same gambit of tests from the fist post, this time with the personList, we'll still get the same results. The reason this is ok, is because now we know that no matter what happens, whatever comes out of that list MUST be a person as well as whatever it actually is. It's not quite the same as being able to cast, but remember that the whole point is to avoid casting around so I think this is the best we can do.

The only problem I see with this is that you aren't going to be able to create a single method that you can hand a generic list Of Employees that will treat them as a generic list Of Person so you'll be stuck overloading out the wazoo to handle each kind of PersonList you need to handle, right? Luckily, the dev put one more trick up our sleeves to help us out: Generic.List.ConvertAll()

Visual Basic:
    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
        Dim employees As New EmployeeList(Of Employee)
        employees.Add(New Employee)
'
        MessageBox.Show(employees(0).name)
        MessageBox.Show(employees(0).Job)
'
        Dim people As Object = employees.ConvertAll(New Converter(Of Employee, Person)(AddressOf EmployeeToPerson)) ''normally, you'd strongly type, of course.
'
        Dim isSameType = TypeOf people Is Generic.List(Of Employee)
        Console.WriteLine(isSameType.ToString) ' <-- Returns False
'
        isSameType = TypeOf people Is Generic.List(Of Person)
        Console.WriteLine(isSameType.ToString) ' <-- Returns True
'
    End Sub
'
    Private Function EmployeeToPerson(ByVal worker As Employee) As Person
        Return DirectCast(worker, Person)
    End Function

So, now with the power to convert a generic type to whatever we need it to be (in this case the base class) we can still write a method that will take a list of people and use it on lists of employees, clients, whatever. I think this is the best way to use generics while dealing with the constraints that generics place on the types they hold. It's not quite the same as we are used to, but it is certainly similarly enough. And think about all the extra compile time type safety you get...

From a best practices standpoint, where would you put your conversion function and how might you do it? I would be inclined to put the above conversion method in the Employee class and make it static. Any other thougts?
 
...
2. Decreasing the performance impact of boxing/unboxing specific types to and from Object

But boxing an unboxing refers to making a value type a reference type and vice versa...

Your examples are all reference types and therefore nothing is boxed or unboxed, simply referenced by their base classes which is perfectly acceptable and the performance impact of that cast is minimal if nothing. It's really no differenant in saying a person is an employee and referencing an instance of an employee as a person.
 
C# Language Specification 2.0 said:
While the use of type object makes the Stack class very flexible, it is not without drawbacks. For example, it is possible to push a value of any type, such a Customer instance, onto a stack. However, when a value is retrieved, the result of the Pop method must explicitly be cast back to the appropriate type, which is tedious to write and carries a performance penalty for run-time type checking:
C#:
Stack stack = new Stack();
stack.Push(new Customer());
Customer c = (Customer)stack.Pop();
If a value of a value type, such as an int, is passed to the Push method, it is automatically boxed. When the int is later retrieved, it must be unboxed with an explicit type cast:
C#:
Stack stack = new Stack();
stack.Push(3);
int i = (int)stack.Pop();
Such boxing and unboxing operations add performance overhead since they involve dynamic memory allocations and run-time type checks.

What you say is true. It seems that I made an error when typing and got ahead of myself. Sorry. :) I'll add the type check hit in above to make sure it doesn't remain confusing. Good catch. And I don't make this stuff up...this is the reasoning that Microsoft gives for why generics are beneficial. Whether the reasons are good ones, I suppose, depends on your point of view and specific situation and need.

I know we were talking about VB before, but these seem to be pretty universal concepts so I hope you don't mind a little C#.


Edit to Post #6
The two reasons for why generics are beneficial should read:
But when you think about the problem that generics are meant to solve, specifically
1. Finding compile time errors before they become runtime errors (preventing bad cast exceptions) and
2. Decreasing the performance impact of boxing/unboxing value types to and from Object (example: adding an int to a regular ArrayList) and type conversion in general (example: adding an Employee to a regular ArrayList)
Sorry for the confusion.
 
Last edited:
I don’t think there was much confusion, really...

For Value types the advantage is generally execution speed. (One isn't usually coercing between value types.) For Reference types the advantage is strong typing so that you don't have to cast; the problem here is not so much execution speed, but the possibility of programmer error. Generics allow us to make a strong-typed collection class in one line; it really is beautiful.

But back to the issue at hand...

I think we just have to accept that Generics really are just "Templates" and should be thought of (more or less) as a short-cut to our actually having to Copy-Paste code and then refactor to change the data type. That's all that Generics are really doing for us... So a List(Of Person) versus a List(Of Employee) really is just a Copy-Paste / Find-Replace operation. While we know that the two inner-classes are inheriting, the outer List classes are really more of a copy-paste construction - no inheritance.

Here was my next attempt at how this might be handled, although I will not in advance that is fails. But here was the attempt:
Visual Basic:
Class BaseList(Of BaseType)
    Inherits System.Collections.Generic.List(Of BaseType)
End Class

Class DerivedList(Of BaseType, DerivedType As BaseType)
    Inherits BaseList(Of BaseType)
    Sub Add(ByVal newItem As DerivedType)
        MyBase.Add (newItem)
    End Sub
    Function Item(ByVal index As Integer) As DerivedType
        Return DirectCast(MyBase.Item(index), DerivedType)
    End Function
End Class
There is very little code required to code here because the BaseList(Of BaseType) inherits from Generic.List(Of BaseType) and the DerivedList(Of BaseType, DerivedType As BaseType) inherits from BaseList(Of BaseType).

Now, within the DerivedList(Of BaseType, DerivedType As BaseType) we have to overload the members that we wish to strong-type 'As DerivedType'. But we only have to do this once. For our PersonList and Employee list all we need to do is the following:
Visual Basic:
Class PersonList
    Inherits BaseList(Of Person)
End Class

Class EmployeeList
    Inherits DerivedList(Of Person, Employee)
End Class
That's it!

But it does not *quite* work. In VB.NET, I attempted to run the following:
Visual Basic:
Shared Sub TestIt()
    Dim pL As New PersonList
    Dim eL As New EmployeeList

    MessageBox.Show((TypeOf (pL) Is PersonList).ToString)
    MessageBox.Show((TypeOf (eL) Is PersonList).ToString)   ' *** Error ***
    MessageBox.Show((TypeOf (pL) Is EmployeeList).ToString) ' *** Error ***
    MessageBox.Show((TypeOf (eL) Is EmployeeList).ToString)
End Sub
But the two lines marked "*** Error ***" failed to compile, with the message stating "Expression of type 'EmployeeList' can never be of type 'PersonList'.".

Hmmm... oh, well, it was worth a shot.

By the way, it also will not compile in C# either, but for a different reason... I'll put that in the next post.

-- Mike
 
Ok, I was wrong, in C# the results are substantially the same. Here are the two key classes:
C#:
public class BaseList<BaseType> : System.Collections.Generic.List<BaseType>
{
}

public class DerivedList<BaseType, DerivedType> : BaseList<BaseType> where DerivedType : BaseType
{
    public void Add(DerivedType item)
    {
        base.Add(item);
    }
    public DerivedType Item(int index)
    {
        return (DerivedType)base[index];
    }
}
Ok, and now our PersonList and EmployeeList classes:
C#:
class PersonList : BaseList<Person>
{
}

class EmployeeList : DerivedList<Person, Employee>
{
}
And now our testing routine:
C#:
void RunIt
{
    PersonList pList = new PersonList();
    EmployeeList eList = new EmployeeList();

    MessageBox.Show((pList is PersonList).ToString());    // True
    MessageBox.Show((eList is PersonList).ToString());    // *** False ***
    MessageBox.Show((pList is EmployeeList).ToString());  // False
    MessageBox.Show((eList is EmployeeList).ToString());  // True
}
Using C#, the 2nd and 3rd lines in the above give a light-warning whereas in VB.NET it is a stronger compiler error and won't run. Running it in C# returns the results shown above, where 'eList is PersonList' returns False. Oh well, it was worth a shot.

Although theoretically interesting to kick this around... I can't imagine that this could actually ever be a problem in anyone's code. It is interesting that Arrays *CAN* exhibit this kind of "Container Inheritance" and in effect are exhibiting multiple inheritance if you think about it -- inheriting from the Array class as well as from the inner-type as well. :cool:
 
Back
Top