none
Static code down-cast vs. dynamic generic down-cast? RRS feed

  • Question

  • Hi,

    I have found a case where I can't explain why the run-time behavior is different. It would be greatly appreciated, if someone can bump my understanding a little. 

    1) I have some IEntity implementations A and B and a manager implementation S. S has 2 ways of branching a general call-method to a specialized call-method (dependent on implementation type e.g. A or B): 

        interface IEntity
        {
        }
        class A : IEntity
        {
        }
        class B : IEntity
        {
        }
    
        class S
        {
            public bool useDynamicGeneric;
    
            public int eCalls;
            public int eCallDepth;
            /// <summary>
            /// Branch-out to specialized Call-method per implementation type.
            /// </summary>
            /// <param name="entity"></param>
            public void Call(IEntity entity)
            {
                Trace.WriteLine("Call(e): " + entity);
                if (0 < this.eCallDepth)
                {
                    throw new InvalidOperationException(
                        "Oh why? ... recursive call to generic Call-method!");
                }
                ++this.eCallDepth;
                ++this.eCalls;
                if (!useDynamicGeneric)
                { // this is the brute force way
                    string typeName = entity.GetType().Name;
                    switch (typeName)
                    {
                        case "A":
                            {
                                this.Call(entity as A);
                            }
                            break;
                        case "B":
                            {
                                this.Call(entity as B);
                            }
                            break;
                        default:
                            {
                                throw new NotSupportedException(typeName);
                            }
                    }
                }
                else
                {   
                    // this is a smart but expensive way... 
                    // it doesn't work even though entity seems to be correctly down-casted
                    // Why?
                    Type implementationType = entity.GetType();
                    MethodInfo methodInfo = this.GetType().GetMethod(
                        "CallWithCast", 
                        BindingFlags.Public | BindingFlags.Instance);
                    MethodInfo genericMethodInfo = methodInfo.MakeGenericMethod(
                        implementationType);
                    genericMethodInfo.Invoke(this, new object[] { entity });
                }
                --this.eCallDepth;
            }
    
            public int aCalls;
            public void Call(A a)
            {
                Trace.WriteLine("Call(a): " + a);
                ++this.aCalls;
            }
    
            public int bCalls;
            public void Call(B b)
            {
                Trace.WriteLine("Call(b): " + b);
                ++this.bCalls;
            }
    
            public void CallWithCast<T>(IEntity entity) where T : IEntity
            {
                T casted = (T)entity;
                Trace.WriteLine("CallWithCast: " + entity
                    + " down-casted to " + casted + "?");
                this.Call(casted); // expected this to work
            }
        }

    2) I have the following test passing:

           /// <summary>
            /// This test passes... it uses an ugly switch in general Call-method.
            /// </summary>
            [Test]
            public void StaticCodeTest()
            {
                S s = new S();
                IEntity a = new A();
                IEntity b = new B();
    
                { // general entity Call-method using internal brute force down-casting
                    s.useDynamicGeneric = false;
                    s.Call(a);
                    Assert.AreEqual(1, s.eCalls);
                    Assert.AreEqual(1, s.aCalls);
                    Assert.AreEqual(0, s.bCalls);
                }
                { // Use specialized Call-method using down-cast on calling side
                    s.Call(a as A);
                    Assert.AreEqual(1, s.eCalls);
                    Assert.AreEqual(2, s.aCalls);
                    Assert.AreEqual(0, s.bCalls);
                }
                { // Use specialized Call-method using down-cast on calling side
                    s.Call((A)a);
                    Assert.AreEqual(1, s.eCalls);
                    Assert.AreEqual(3, s.aCalls);
                    Assert.AreEqual(0, s.bCalls);
                }
            }

    Output:

    Call(e): Org.Common.FailFast.UnitTest.Unknowns.A
    Call(a): Org.Common.FailFast.UnitTest.Unknowns.A
    Call(a): Org.Common.FailFast.UnitTest.Unknowns.A
    Call(a): Org.Common.FailFast.UnitTest.Unknowns.A

    3) The following test fails:

           /// <summary>
            /// This test fails... it uses dynamic generic.
            /// </summary>
            [Test]
            public void DynamicGenericTest()
            {
                S s = new S();
                IEntity a = new A();
                IEntity b = new B();
    
                { // Try to use smart down-casting with dynamic generic - why does it not work?
                    s.useDynamicGeneric = true;
                    s.Call(a);
                    Assert.AreEqual(1, s.eCalls);
                    Assert.AreEqual(1, s.aCalls);
                    Assert.AreEqual(0, s.bCalls);
                }
            }

    Output:

    Call(e): Org.Common.FailFast.UnitTest.Unknowns.A
    CallWithCast: Org.Common.FailFast.UnitTest.Unknowns.A down-casted to Org.Common.FailFast.UnitTest.Unknowns.A?
    Call(e): Org.Common.FailFast.UnitTest.Unknowns.A
    Test 'Org.Common.FailFast.UnitTest.Unknowns.StaticCodeVsDynamicGenericTest.DynamicGenericTest' failed: System.Reflection.TargetInvocationException : Exception has been thrown by the target of an invocation.
      ----> System.InvalidOperationException : Oh why? ... recursive call to generic Call-method!
    	at System.RuntimeMethodHandle._InvokeMethodFast(Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)
    	at System.RuntimeMethodHandle.InvokeMethodFast(Object target, Object[] arguments, Signature sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)
    	at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
    	at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
    	at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
    	UnitTest\Unknowns\StaticCodeVsDynamicGenericTest.cs(70,0): at Org.Common.FailFast.UnitTest.Unknowns.S.Call(IEntity entity)
    	UnitTest\Unknowns\StaticCodeVsDynamicGenericTest.cs(144,0): at Org.Common.FailFast.UnitTest.Unknowns.StaticCodeVsDynamicGenericTest.DynamicGenericTest()
    	--InvalidOperationException
    	UnitTest\Unknowns\StaticCodeVsDynamicGenericTest.cs(33,0): at Org.Common.FailFast.UnitTest.Unknowns.S.Call(IEntity entity)
    	UnitTest\Unknowns\StaticCodeVsDynamicGenericTest.cs(94,0): at Org.Common.FailFast.UnitTest.Unknowns.S.CallWithCast[T](IEntity entity)

    What is the reason to this behavior?

    Please note that I first and foremost need to learn and understand this behavior.

    I can solve the design problem in other ways.

    Thanks in advance.

    Kind Regards,

    Keld Ølykke


    Tuesday, June 18, 2013 3:37 PM

Answers

  • This basically has to do with the way overload resolution is performed: at compile time, not at runtime.

    When presented with the line:

    Call(casted); // expected this to work

    the compiler has only one choice, to call Call(IEntity). All it knows about T is that it IEntity. At runtime T may be A, B or any other type that implements IEntity but the compiler doesn't care about that and can't care about that. It would have to emit a different CallWithCast method for every type for which 'Call' is overloaded.

    Tuesday, June 18, 2013 3:50 PM
    Moderator

All replies

  • This basically has to do with the way overload resolution is performed: at compile time, not at runtime.

    When presented with the line:

    Call(casted); // expected this to work

    the compiler has only one choice, to call Call(IEntity). All it knows about T is that it IEntity. At runtime T may be A, B or any other type that implements IEntity but the compiler doesn't care about that and can't care about that. It would have to emit a different CallWithCast method for every type for which 'Call' is overloaded.

    Tuesday, June 18, 2013 3:50 PM
    Moderator
  • Here's the standard explanation of this from Eric Lippert:

    http://blogs.msdn.com/b/ericlippert/archive/2009/07/30/generics-are-not-templates.aspx

    David


    David http://blogs.msdn.com/b/dbrowne/

    Tuesday, June 18, 2013 4:13 PM
  • Mike is exactly right here - and that explains why this doesn't work the way you expect.

    That being said, C# 4 does provide you with an alternative method to handle this which will work correctly - using runtime binding:

    public void Call(IEntity entity)
    {
    	dynamic dispatcher = entity;
    	this.Call(dispatcher);
    }
    

    Not only is this code incredibly short and simple, it does the dispatching to the "best" overload correctly, and does so more efficiently than either of your reflection based approaches (due to the dynamic caching).


    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, June 18, 2013 4:15 PM
    Moderator
  • "It would have to emit a different CallWithCast method for every type for which 'Call' is overloaded."

    Yes. That was what I expected... coming from C++ ... thought I knew most C# details now.

    Come to think of it, I have explained to others that generics are compiled into lots of signatures derived at compile time. Have to get to them and pull that back :)

    Thanks for the revelation!   


    Tuesday, June 18, 2013 4:50 PM
  • This is a great link. Thanks!
    Tuesday, June 18, 2013 4:52 PM
  • "Not only is this code incredibly short and simple, it does the dispatching to the "best" overload correctly, and does so more efficiently than either of your reflection based approaches (due to the dynamic caching)." 

    This is what I would like to have yes..... As I hint in your words this would be the optimal overload strategy... any chance to have this as an engine preference?  


    Tuesday, June 18, 2013 4:57 PM

  • This is what I would like to have yes..... As I hint in your words this would be the optimal overload strategy... any chance to have this as an engine preference?  


    What do you mean "any chance to have this as an engine preference?"

    This is just the behavior when you use dynamic in C#.  The dynamic keyword performs runtime binding based on the dynamic, runtime type information, which is exactly what you're trying to accomplish yourself.


    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, June 18, 2013 4:59 PM
    Moderator
  • What do you mean "any chance to have this as an engine preference?"

    I mean, if I can use the dynamic keyword to use an intelligent overload functionality, it would be pretty impressive, if I could "just" turn this overload functionality on per engine instance, so it would be the default overload functionality. In this way the programmers could get this "better" overload functionality as a default behavior.

    I get that this might not the normal way of changing a language, but it might be that C# would be "better", if  this overload functionality (nailed by the dynamic keyword) would become the default behavior.

    I clearly do not know the implications involved - just want C# to stay my ideal OOP language :)

    Kind Regards,

    Keld Ølykke 

    Tuesday, June 18, 2013 6:35 PM
  • That's what you get with dynamic - It just works.  If you want to opt-in by instance, just use:

    if (!useDynamicGeneric)
    { 
      // this is the brute force way
      string typeName = entity.GetType().Name;
      switch (typeName)
      {
         case "A":
           this.Call(entity as A);
           break;
         case "B":
           this.Call(entity as B);
           break;
         default:
           throw new NotSupportedException(typeName);
      }
    }
    else
    {
       // using dynamic dispatching:
       dynamic toDispatch = entity;
       this.Call(toDispatch); 
    }

    Note that you can make your first version cleaner and better performing by avoiding reflection.  Try using:

    if (!useDynamicGeneric)
    {
      A asA = entity as A;
      if (asA != null)
      {
         this.Call(asA);
         return;
      } 
    
      B asB = entity as B;
      if (asB != null)
      {
         this.Call(asB);
         return;
      } 
    
      throw new NotSupportedException(entity.GetType().Name);
    }
    else
    {
       // using dynamic dispatching:
       dynamic toDispatch = entity;
       this.Call(toDispatch); 
    }



    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, June 18, 2013 6:48 PM
    Moderator
  • Hi Reed,

    Thx for the example. I get the dynamic keyword. 

    I didn't succeed in getting my point through though. Here it goes with a code example instead :)

     

    1) Lets imagine I have the following code:

       interface IEntity
        {
        }
        class A : IEntity
        {
        }
        class B : IEntity
        {
        }
    
        class S
        {
            /// <summary>
            /// If this is called then the implementation type is NOT supported.
            /// </summary>
            /// <param name="entity"></param>
            public void Call(IEntity entity)
            {
                throw new InvalidOperationException(
                        entity.getType() + " is not supported. Please extend this by adding a specialized Call-method for this type.");
            }
    
            public int aCalls;
            public void Call(A a)
            {
                Trace.WriteLine("Call(a): " + a);
                ++this.aCalls;
            }
    
            public int bCalls;
            public void Call(B b)
            {
                Trace.WriteLine("Call(b): " + b);
                ++this.bCalls;
            }
        }

    2) Lets imagine I have the following test:

            /// <summary>
            /// It would be really nice if this test would pass in a future C# language version... IMHO
            /// </summary>
            [Test]
            public void StaticCodeTest()
            {
                S s = new S();
                IEntity a = new A();
                IEntity b = new B();
    
                { // Call-method using automatic clever overload functionality like if explicitly using the dynamic keyword
                    s.Call(a);
                    Assert.AreEqual(0, s.eCalls);
                    Assert.AreEqual(1, s.aCalls);
                    Assert.AreEqual(0, s.bCalls);
                }
            }

    That was what I meant by having an engine that uses the functionality of the dynamic keyword by default. In above example I get what I want without explicitly use of the dynamic keyword. 


    Kind Regards,

    Keld Ølykke


    • Edited by KeldØlykke Tuesday, June 18, 2013 10:47 PM edit
    Tuesday, June 18, 2013 10:46 PM
  • Nope - you need to modify it to "opt in" - it doesn't take much, though. C# is still a typed language - dynamic languages often work more like that - but in C#, you have to use dynamic to get the appropriate method, ie:

     [Test]
            public void StaticCodeTest()
            {
                S s = new S();
                dynamic a = new A();
                dynamic b = new B();
    
                { // Call-method using automatic clever overload functionality like if explicitly using the dynamic keyword
                    s.Call(a);
                    Assert.AreEqual(0, s.eCalls);
                    Assert.AreEqual(1, s.aCalls);
                    Assert.AreEqual(0, s.bCalls);
                }
            }


    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, June 18, 2013 11:11 PM
    Moderator
  • Even though - make no mistake - I do appreciate the dynamic keyword :)

    Thx for your answers

    Tuesday, June 18, 2013 11:24 PM