C# Generic: Is this a bug or by design
-
Given the following class hierarchy and simple Program.Main using System; using System.Collections.Generic; using System.Text; namespace GenericTest { class Base { public void Name() { Console.WriteLine("I'm a Base class"); } }; class Middle : Base { public new void Name() { Console.Write("I'm a Middle class"); } }; class Child : Middle { public new void Name() { Console.Write("I'm a Child class"); } }; class Program { static void PrintName( T instance ) where T : GenericTest.Base { instance.Name(); } static void Main(string[] args) { Child c = new Child(); PrintName( c ); } } } I see the following output I'm a Base class And I would have expected to see I'm a Child class Is this a bug with Generics or is it a side effect of how the Generic is generated for reference types? Thanks Matt Schuckmann
-
Given the following class hierarchy and simple Program.Main using System; using System.Collections.Generic; using System.Text; namespace GenericTest { class Base { public void Name() { Console.WriteLine("I'm a Base class"); } }; class Middle : Base { public new void Name() { Console.Write("I'm a Middle class"); } }; class Child : Middle { public new void Name() { Console.Write("I'm a Child class"); } }; class Program { static void PrintName( T instance ) where T : GenericTest.Base { instance.Name(); } static void Main(string[] args) { Child c = new Child(); PrintName( c ); } } } I see the following output I'm a Base class And I would have expected to see I'm a Child class Is this a bug with Generics or is it a side effect of how the Generic is generated for reference types? Thanks Matt Schuckmann
Hi, it is correct by definition of the C# language. You tell PrintName it is accepting objects of type Base, hence instance.Name() actually is a call to Base.Name(). Exactly the same thing would happen when you do:
static void SomeMethod(Base instance) {
instance.Name();
}If you want to get Child.Name() you should make Name() virtual and override it, instead of using the "new" keyword. :)
Luc Pattyn [My Articles] [Forum Guidelines]
-
Hi, it is correct by definition of the C# language. You tell PrintName it is accepting objects of type Base, hence instance.Name() actually is a call to Base.Name(). Exactly the same thing would happen when you do:
static void SomeMethod(Base instance) {
instance.Name();
}If you want to get Child.Name() you should make Name() virtual and override it, instead of using the "new" keyword. :)
Luc Pattyn [My Articles] [Forum Guidelines]
The wayI understood the where statement is that I'm telling it to accept objects derived from type Base or that have the same interface as type Base. If I wanted to tell it to accept objects of type Base I wouldn't use a Generic I'd just use a simple method, like you suggested. So your basically telling me that within a Generic you lose all static type information and everything is staticly bound and treated as if it was the type specified in the where statement. This is really sad your basicly telling me that a generic is no better than a class or method that takes a common ancestor class for whatever types you're going to pass it. I understand making the method virtual to accomplish the intended results of this example but for reasons out side of this example I don't want the method to be virtual and I wanted to use the hiding semantics of the new keyword. Thank You Matt S.
-
The wayI understood the where statement is that I'm telling it to accept objects derived from type Base or that have the same interface as type Base. If I wanted to tell it to accept objects of type Base I wouldn't use a Generic I'd just use a simple method, like you suggested. So your basically telling me that within a Generic you lose all static type information and everything is staticly bound and treated as if it was the type specified in the where statement. This is really sad your basicly telling me that a generic is no better than a class or method that takes a common ancestor class for whatever types you're going to pass it. I understand making the method virtual to accomplish the intended results of this example but for reasons out side of this example I don't want the method to be virtual and I wanted to use the hiding semantics of the new keyword. Thank You Matt S.
mschuckmann wrote:
The wayI understood the where statement is that I'm telling it to accept objects derived from type Base or that have the same interface as type Base.
That is correct; and it also applies to SomeMethod() in my example.
mschuckmann wrote:
within a Generic you lose all static type information
That is not correct: you dont loose all information, both the Generic method and the SomeMethod method accept Base objects or Base derivatives, and these objects keep their characteristics. It is only the way you define your classes (thru the virtual/override/new keywords) that decides whether you get the functionality of the base method or the more specialized method. That has nothing to do with generics. C# offers two ways to get the specialized method: by using the specialized type (Child.Name), or by using virtual methods.
mschuckmann wrote:
a generic is no better than a class or method that takes a common ancestor class
That is correct if you use where T : type. But generics can be used without type constraint (acting somewhat like C++ templates). And they can be used with more complex constraints; read up on "where" in MSDN for that. :)
Luc Pattyn [My Articles] [Forum Guidelines]
-
mschuckmann wrote:
The wayI understood the where statement is that I'm telling it to accept objects derived from type Base or that have the same interface as type Base.
That is correct; and it also applies to SomeMethod() in my example.
mschuckmann wrote:
within a Generic you lose all static type information
That is not correct: you dont loose all information, both the Generic method and the SomeMethod method accept Base objects or Base derivatives, and these objects keep their characteristics. It is only the way you define your classes (thru the virtual/override/new keywords) that decides whether you get the functionality of the base method or the more specialized method. That has nothing to do with generics. C# offers two ways to get the specialized method: by using the specialized type (Child.Name), or by using virtual methods.
mschuckmann wrote:
a generic is no better than a class or method that takes a common ancestor class
That is correct if you use where T : type. But generics can be used without type constraint (acting somewhat like C++ templates). And they can be used with more complex constraints; read up on "where" in MSDN for that. :)
Luc Pattyn [My Articles] [Forum Guidelines]
Luc Pattyn wrote:
That is not correct: you dont loose all information, both the Generic method and the SomeMethod method accept Base objects or Base derivatives, and these objects keep their characteristics.
The objects themselves may keep their characteristics, because after all they are objects, but they no longer behave like themselves the with respect to non-virtual methods and this is counter intuitive to anyone who has done extensive template programing.
Luc Pattyn wrote:
It is only the way you define your classes (thru the virtual/override/new keywords) that decides whether you get the functionality of the base method or the more specialized method. That has nothing to do with generics.
But the symantics of how they've implimented the generic feature makes the object behave differently with in the code of the generic (effectively making it lose it's type within the Generic), what is the point of providing the generic with specific type information (i.e. printName(c) ) on the type I'm passing it if it's just going to ignore it and staticely the object to the lowest common denominator implimentation for non-virtual methods. In my opinion this hugely limits the functionality of generics when compared to templates and I find it hard to believe that anyone actually thinks this is good behavior.
Luc Pattyn wrote:
That is correct if you use where T : type. But generics can be used without type constraint (acting somewhat like C++ templates). And they can be used with more complex constraints; read up on "where" in MSDN for that.
If you leave out the where constraint you've essentially specified a generic with a constraint of object and you can't call any methods except those declared by object. Therefore my example code will not compile without the where constraint. So sure you can impliment generics without type constraints but don't expect to do anything interesting in your generic code. C++ templates effectively impliment constraints but much more flexibly and without any new confusing and limiting syntax. They do this by verifying any type they are instantiated with impliment (or claim to impliment) any methods that are is used by the template at compile time. Thanks again Matt S.
-
Luc Pattyn wrote:
That is not correct: you dont loose all information, both the Generic method and the SomeMethod method accept Base objects or Base derivatives, and these objects keep their characteristics.
The objects themselves may keep their characteristics, because after all they are objects, but they no longer behave like themselves the with respect to non-virtual methods and this is counter intuitive to anyone who has done extensive template programing.
Luc Pattyn wrote:
It is only the way you define your classes (thru the virtual/override/new keywords) that decides whether you get the functionality of the base method or the more specialized method. That has nothing to do with generics.
But the symantics of how they've implimented the generic feature makes the object behave differently with in the code of the generic (effectively making it lose it's type within the Generic), what is the point of providing the generic with specific type information (i.e. printName(c) ) on the type I'm passing it if it's just going to ignore it and staticely the object to the lowest common denominator implimentation for non-virtual methods. In my opinion this hugely limits the functionality of generics when compared to templates and I find it hard to believe that anyone actually thinks this is good behavior.
Luc Pattyn wrote:
That is correct if you use where T : type. But generics can be used without type constraint (acting somewhat like C++ templates). And they can be used with more complex constraints; read up on "where" in MSDN for that.
If you leave out the where constraint you've essentially specified a generic with a constraint of object and you can't call any methods except those declared by object. Therefore my example code will not compile without the where constraint. So sure you can impliment generics without type constraints but don't expect to do anything interesting in your generic code. C++ templates effectively impliment constraints but much more flexibly and without any new confusing and limiting syntax. They do this by verifying any type they are instantiated with impliment (or claim to impliment) any methods that are is used by the template at compile time. Thanks again Matt S.
mschuckmann wrote:
If you leave out the where constraint you've essentially specified a generic with a constraint of object and you can't call any methods except those declared by object. Therefore my example code will not compile without the where constraint. So sure you can impliment generics without type constraints but don't expect to do anything interesting in your generic code.
I do agree to your point of view, that the limitation of generics in regard to derived classes are unexpected. As a workaround you can use an interface, that defines the required methods, as the constraint. However, you must explicitly specify the interface for each class:
interface Named
{
void Name();
};class Base : Named
{
public void Name()
{
Console.WriteLine("I'm a Base class");
}
};class Middle : Base, Named
{
public new void Name()
{
Console.Write("I'm a Middle class");
}
};class Child : Middle, Named
{
public new void Name()
{
Console.Write("I'm a Child class");
}
};class Program
{
static void PrintName<T>( T instance ) where T : Named
{
instance.Name();
}static void Main( string\[\] args ) { Child c = new Child(); PrintName( c ); Console.ReadLine(); }
}
Regards, Tim