Sunday, May 1, 2011

Generics: Accessing New Members, Not Hidden Members

I have run into a problem with generics and new members. I wrote a generic class which operates on an object of type ObjectA. ObjectB derives from ObjectA and hides a few of ObjectA's members. When I supply the type of ObjectB as the type parameter to the generic class, I would expect that when I call any of the members hidden by ObjectB, I would be calling ObjectB's implementation. However, the CLR still calls the hidden members (ObjectA's implementation). This seems illogical because I explicitly provided the type of ObjectB to the generic class. Is this a problem with generics themselves, or am I doing something wrong?

Edit: Unfortunately, I do not have access to ObjectA's source code and the member I want to override is not virtual. If I had access to ObjectA's source code, I would make the member virtual, but as I cannot do so, my only option for "overriding" the member is through the "new" keyword.

class GenericClass<T> where T : ObjectA  
{  
    public void DoWork(T item)  
    {  
        // When type parameter 'T' is ObjectB, should get ObjectB's implementation  
        item.Invoke();  
    }  
}  

class ObjectA  
{
    public void Invoke()  
    {  
        // A's implementation...  
    }  
}

class ObjectB : ObjectA  
{
    public new void Invoke()  
    {  
        // B's implementation...  
    }  
}

static void Main()  
{  
    GenericClass<ObjectB> genericClass = new GenericClass<ObjectB>();  
    ObjectB objectB = new ObjectB();  
    genericClass.DoWork(objectB);  
}

From stackoverflow
  • No. The calls generated by the compiler are to the members it knows about at compile-time. That's the members exposed by ObjectA.

    Any reason you're not using normal inheritance, with virtual/overridden methods?

    Here's another example of the same kind of thing, by the way - the overloaded == operator for strings isn't used, even though T is string in the call to Foo:

    using System;
    
    class Test
    {
        static bool Foo<T>(T first, T second)
            where T : class
        {
            return first == second;
        }
    
        static void Main()
        {
            string x = "hello";
            string y = new string(x.ToCharArray());
    
            Console.WriteLine(Foo(x, y));
        }
    }
    
    Zach Johnson : Thanks for your answer. I would use virtual/overridden methods, but I am inheriting from System.Windows.Form and trying to write my own implementation for a member that is not virtual.
    Jon Skeet : Then you're not going to be able to access it like this. It sounds like you're trying to make it "nearly virtual" - it doesn't work like that.
    Pontus Gagge : Hm. I'd agree this is somewhat unintuitive, if you're used to C++ templates. C# does separate compilation of template classes, and knows only about ObjectA at that time. When the template is instantiated, the instantiated class is not recompiled with the added knowledge of ObjectB.
  • This may not be an answer to your question, but I don't see the point with your approach (it may be because I only see a simplified example, though).

    I would suggest using the following approach:

    class ObjectA
    {
        public virtual void Invoke()
        {
            // do some work
        }
    }
    
    class ObjectB : ObjectA
    {
        public override void Invoke()
        {
            // do some other work
        }
    }
    
    class GenericNotNeededClass
    {  
        public void DoWork(ObjectA item)  
        {  
            item.Invoke();  
        }  
    }  
    
    
    static void Main()  
    {  
        GenericNotNeededClass nonGenericClass = new GenericNotNeededClass();  
        ObjectB objectB = new ObjectB();  
        nonGenericClass.DoWork(objectB);
    }
    

    I would believe that code does what you are looking for, based on your example code.

  • You define T to be of type ObjectA for your generic. If Invoke() were virtual it would work the way you are thinking, but because it is not, your generic calls the ObjectA implementation because that is what T is defined to be.

    There is no virtual method table entry to point to the ObjectB implementation of Invoke() so this is all the runtime can call. If it were a virtual method there would be a method address in the VMT and it would act like you were thinking that it would.

0 comments:

Post a Comment