locked
I do not know if C# has a bug in its generic implementation

    Question

  • For the following code:

    class B {
         public String G() { return "B.G()"; }
    }  

    class D : B {
         public String G() { return "D.G()"; }
    }  

    class TestCompile {
         private static String TestG<T>(T b) where T: B {return b.G(); }
         static void Main(string[] args) { TestG(new D()); }
    }

    The result is B.G(), whereas the result of similar C++ or java code would be D.G().

    Tuesday, November 29, 2011 5:06 PM

Answers

  • What you are describing is not how generics in the CLR works.

     

    While generics appear similar to templates in C++, in the CLR, they're a very different animal.  Generics aren't a compile time construct in the CLR - they're a runtime type all on their own.

     

    When you create a generic constraint, as you did here, like so:

     

    private static String TestG<T>(T b) where T: B 
    


    This will cause the compiler to allow you to set any "B" or subclass of "B" as the type used.  However, the method calls using the members of B are called using B's method lookup, so the call inside (b.G();) is resolved using "B" as the type.

    If you look at the IL generated, you'll see this in action.  The actual call (b.G()) is translated into IL as:

    callvirt instance string Test.B::G()
    


    As you can see, it's calling an instance method (using callvirt, to handle virtual methods correctly, even if the method is not defined virtual) which returns a string, but scoping it to Test.B::G.  

    This is how generic method resolution works in the CLR - the constraint and the type will cause the runtime type of "b" to always be D in this case, but the method calls will always be scoped based on the constraint (B).

     

     

     

    Now, in your case, you say your goal is:

    "Clearly, the intention of the code is to activate D.G()--I am trying to resolve method overriding at compile time using generic rather than at runtime using v-table, thus I am not using virtual method here.  This is a technique which I borrowed from C++."

    If this is the goal, I'd recommend a completely different approach.  Generics will not work, as they are not a compile time substitution like templates in C++.  In fact, this only works in Java because the methods are virtual by default - the type erasure would cause the same behavior if the method was not virtual...

    In C#, a good approach for this is to use dynamic instead of generics.  dynamic will cause this to push to runtime, but give you a non-vtable lookup and provide the behavior you're trying to get.  Try the following:

    class B
    {
        public String G() { return "B.G()"; }
    }
    
    class D : B
    {
        public String G() { return "D.G()"; }
    }
    
    class TestCompile
    {
        private static String TestG(dynamic b)
        {
            return b.G();
        }
    
        static void Main(string[] args)
        {
            Console.WriteLine(TestG(new D()));
            Console.ReadKey();
        }
    } 
    


    Now, the unfortunately side effect here is that we lose the generic constraint.  This could be added back in, but would require a bit of editing to make it work properly.  For example, if you want to keep the same usage/API as before, you can use:

     

    private static String TestG<T>(T b) where T: B
    {
        dynamic tmp = b;
        return tmp.G();
    }
    
    

    Here, we're keeping the same generic constraints and same API, but executing it differently.  Instead of executing using the generic implementation, we set the value to a dynamic, which causes runtime method lookup instead of a normal method call.  As a result, "D.G()" will be printed.

     

    A fully functional example is as follows:

    namespace Test
    {
        using System;
    
        internal class B
        {
            public String G()
            {
                return "B.G()";
            }
        }
    
        internal class D : B
        {
            public String G()
            {
                return "D.G()";
            }
        }
    
        internal class TestCompile
        {
            private static String TestG<T>(T b) where T : B
            {
                dynamic tmp = b;
                return tmp.G();
            }
    
            private static void Main(string[] args)
            {
                Console.WriteLine(TestG(new D()));
                   // Now prints "D.G()"
                Console.ReadKey();
            }
        }
    }
    



    Reed Copsey, Jr. - http://reedcopsey.com
    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".
    • Proposed as answer by Tim Copenhaver Tuesday, November 29, 2011 6:47 PM
    • Marked as answer by Chengpu Wang Tuesday, November 29, 2011 7:53 PM
    Tuesday, November 29, 2011 6:37 PM
    Moderator

All replies

  • Here is the equivalent java code:

        public class B {
            public String G() { return "B.G()"; }
        }
       
       
        public class D extends B {
            public String G() { return "D.G()"; }
        }
       
       
        public class TestCompile {
            private static <T extends B> String TestG(T b) {
                return b.G();
            }
            public static void main(String[] args) {
                TestG(new D());
            }
        }

     

     

    Tuesday, November 29, 2011 5:14 PM
  • This is actually a difference in how C# and Java treat members.

     

    In Java, methods are virtual by default.  In C#, they are not.


    In fact, in this case, the compiler actually issues a warning that explains the behavior you're seeing:

    "Warning 1 'Test.D.G()' hides inherited member 'Test.B.G()'. Use the new keyword if hiding was intended."

     

    This is because you didn't make the methods virtual.  To write the equivalent C# code to your Java code, you'd need to write this as:

    class B
    {
        // Make virtual (like Java)
        public virtual String G() { return "B.G()"; }
    }
    
    class D : B
    {
        // Override this now, so it's not hiding
        public override String G() { return "D.G()"; }
    }
    


    This will print "D.G()", as you're expecting.

     

     


    Reed Copsey, Jr. - http://reedcopsey.com
    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".
    Tuesday, November 29, 2011 5:34 PM
    Moderator
  • If D.G() hides B.G(), D.G() should be called.  But instead, B.G() is called. Even you add a new for D.G() to get rid of the warning, the result is still the same.

    Clearly, the intention of the code is to activate D.G()--I am trying to resolve method overriding at compile time using generic rather than at runtime using v-table, thus I am not using virtual method here.  This is a technique which I borrowed from C++.

    According to the spec of where: "where T : <base class name>: The type argument must be or derive from the specified base class." Thus "where T: B" is not an instantiation, so that I am expecting a reference of D being instantiated here.  Instead, it seems that reference of B being instantiated here after "where T: B".

     

    Tuesday, November 29, 2011 6:03 PM
  • What you are describing is not how generics in the CLR works.

     

    While generics appear similar to templates in C++, in the CLR, they're a very different animal.  Generics aren't a compile time construct in the CLR - they're a runtime type all on their own.

     

    When you create a generic constraint, as you did here, like so:

     

    private static String TestG<T>(T b) where T: B 
    


    This will cause the compiler to allow you to set any "B" or subclass of "B" as the type used.  However, the method calls using the members of B are called using B's method lookup, so the call inside (b.G();) is resolved using "B" as the type.

    If you look at the IL generated, you'll see this in action.  The actual call (b.G()) is translated into IL as:

    callvirt instance string Test.B::G()
    


    As you can see, it's calling an instance method (using callvirt, to handle virtual methods correctly, even if the method is not defined virtual) which returns a string, but scoping it to Test.B::G.  

    This is how generic method resolution works in the CLR - the constraint and the type will cause the runtime type of "b" to always be D in this case, but the method calls will always be scoped based on the constraint (B).

     

     

     

    Now, in your case, you say your goal is:

    "Clearly, the intention of the code is to activate D.G()--I am trying to resolve method overriding at compile time using generic rather than at runtime using v-table, thus I am not using virtual method here.  This is a technique which I borrowed from C++."

    If this is the goal, I'd recommend a completely different approach.  Generics will not work, as they are not a compile time substitution like templates in C++.  In fact, this only works in Java because the methods are virtual by default - the type erasure would cause the same behavior if the method was not virtual...

    In C#, a good approach for this is to use dynamic instead of generics.  dynamic will cause this to push to runtime, but give you a non-vtable lookup and provide the behavior you're trying to get.  Try the following:

    class B
    {
        public String G() { return "B.G()"; }
    }
    
    class D : B
    {
        public String G() { return "D.G()"; }
    }
    
    class TestCompile
    {
        private static String TestG(dynamic b)
        {
            return b.G();
        }
    
        static void Main(string[] args)
        {
            Console.WriteLine(TestG(new D()));
            Console.ReadKey();
        }
    } 
    


    Now, the unfortunately side effect here is that we lose the generic constraint.  This could be added back in, but would require a bit of editing to make it work properly.  For example, if you want to keep the same usage/API as before, you can use:

     

    private static String TestG<T>(T b) where T: B
    {
        dynamic tmp = b;
        return tmp.G();
    }
    
    

    Here, we're keeping the same generic constraints and same API, but executing it differently.  Instead of executing using the generic implementation, we set the value to a dynamic, which causes runtime method lookup instead of a normal method call.  As a result, "D.G()" will be printed.

     

    A fully functional example is as follows:

    namespace Test
    {
        using System;
    
        internal class B
        {
            public String G()
            {
                return "B.G()";
            }
        }
    
        internal class D : B
        {
            public String G()
            {
                return "D.G()";
            }
        }
    
        internal class TestCompile
        {
            private static String TestG<T>(T b) where T : B
            {
                dynamic tmp = b;
                return tmp.G();
            }
    
            private static void Main(string[] args)
            {
                Console.WriteLine(TestG(new D()));
                   // Now prints "D.G()"
                Console.ReadKey();
            }
        }
    }
    



    Reed Copsey, Jr. - http://reedcopsey.com
    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".
    • Proposed as answer by Tim Copenhaver Tuesday, November 29, 2011 6:47 PM
    • Marked as answer by Chengpu Wang Tuesday, November 29, 2011 7:53 PM
    Tuesday, November 29, 2011 6:37 PM
    Moderator
  • Does dynamic relies on reflection?  If so, it is more expensive than v-table.
    Tuesday, November 29, 2011 8:03 PM
  • Does dynamic relies on reflection?  If so, it is more expensive than v-table.

    No, it's not using reflection - it's using a cached runtime behavior, which is fairly fast.

     

    It's going to be more expensive than a v-table lookup (if you are just trying to optimize away this, you're going to be out of luck), but still perform quite well (much, much faster than reflection), since the DLR uses some pretty advanced techniques to help performance in this type of scenario.

     

     


    Reed Copsey, Jr. - http://reedcopsey.com
    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".
    Tuesday, November 29, 2011 8:10 PM
    Moderator