5

I need to call method overloads according to the type of object at runtime using c# late binding features. It works fine when all overloads are defined in the same class as the call is happening. But when an overload is defined in a derived class, it won't get bound at runtime.

class BaseT
{}

class DerivedA : BaseT
{}

class DerivedB : BaseT
{}

class Generator
{
    public void Generate(IEnumerable<BaseT> objects)
    {
        string str = "";
        foreach (dynamic item in objects)
        {
            str = str + this.Generate(item); //throws an exception on second item
        }
    }

    protected virtual string Generate(DerivedA a)
    {
        return " A ";
    }        
}

class DerivedGenertor : Generator
{
    protected virtual string Generate(DerivedB b)
    {
        return " B ";
    }
}



class Program
{
    static void Main(string[] args)
    {
        List<BaseT> items = new List<BaseT>() {new DerivedA(), new DerivedB()};
        var generator = new DerivedGenertor();
        generator.Generate(items);
    }
}

Here is another more clear example:

class BaseT
{}

class DerivedA : BaseT
{}

class DerivedB : BaseT
{}

class DerivedC : BaseT
{ }

class Generator
{
    public void Generate(IEnumerable<BaseT> objects)
    {
        string str = "";
        foreach (dynamic item in objects)
        {
            str = str + this.Generate(item); //throws an exception on third item
        }
    }

    public virtual string Generate(DerivedA a)
    {
        return " A ";
    }

    public virtual string Generate(DerivedC c)
    {
        return " C ";
    }
}

class DerivedGenertor : Generator
{
    public virtual string Generate(DerivedB b)
    {
        return " B ";
    }
}



class Program
{
    static void Main(string[] args)
    {
        List<BaseT> items = new List<BaseT>() {new DerivedA(), new DerivedC(), new DerivedB()};
        dynamic generator = new DerivedGenertor();
        generator.Generate(items);
    }
}
Siamak S.
  • 81
  • 1
  • 5

2 Answers2

3

You would need to declare the Generator as dynamic as well so that you have dynamic resolution on the input object and on the method being called. But you will have to change the access modifiers to public or protected internal to do this, because you now have an externally resolved method.

class BaseT
{ }

class DerivedA : BaseT
{ }

class DerivedB : BaseT
{ }

class Generator
{
    public string Generate(IEnumerable<BaseT> objects)
    {
        string str = "";
        dynamic generator = this;
        foreach (dynamic item in objects)
        {
            str = str + generator.Generate(item); 
        }
        return str;
    }

    protected internal virtual string Generate(DerivedA a)
    {
        return " A ";
    }
}

class DerivedGenertor : Generator
{
    protected internal virtual string Generate(DerivedB b)
    {
        return " B ";
    }
}



class Program
{
    static void Main(string[] args)
    {
        List<BaseT> items = new List<BaseT>() { new DerivedA(), new DerivedB() };
        var generator = new DerivedGenertor();
        string ret = generator.Generate(items);
    }
}
Brian Rudolph
  • 6,142
  • 2
  • 23
  • 19
  • I don't see why CLR cannot locate the other overload since the run time type of generator actually has the correct overload. Doesn't CLR search through all methods to find the right one? Is it by design? – Siamak S. Sep 09 '15 at 04:01
  • @SiamakS. No. That is the definition of the difference between a late binding and early binding. CLR does not EVER search through any methods. DLR does. You need to explicitly say you want late binding, because it is so much slower and more bug prone. At the point of compilation (early binding), the compiler only knows about `Generate(DerivedA a)`, hence it uses that. – Aron Sep 09 '15 at 07:42
  • @Aron The compiler doesn't early bind anything because the argument is `dynamic`. If it were to try to bind at compile time it would give a compilation error as there is no valid candidate; not every `BaseT` is a `DerivedA`. Change `dynamic item` to `var item` and the compiler will give you the obvious error. The fact that the error is at runtime points to a late binding. – InBetween Sep 09 '15 at 14:35
  • @inBetween As far as I know declaring it dynamic only changes calls on the dynamic object to be late bound. Passing it to another non-dynamic method will still be a compile-time decision. – Brian Rudolph Sep 09 '15 at 15:38
  • @BrianRudolph Are you sure? in the general case that isn't true. Suppose `Generate` had two valid overloads defined in the base class. How would the compiler decide which of the two at compile time if it doesn't know the type of the argument? In this case there is only one valid candidate so I am not altogether sure if the compiler does in fact early bind it and simply perform a type check at runtime although I'd rather think it doesn't. – InBetween Sep 09 '15 at 15:42
  • @InBetween Agreed. In this case, I'm not sure why this behavior is presenting. I am going to check the IL to see what the compiler is actually doing. – Brian Rudolph Sep 09 '15 at 15:49
2

How do you expect it to get bound? The compiler binds the call this.Generate(item) to the only possible candidate: Generator.Generate(DerivedA a). This has nothing to do with when the binding takes place; DerivedGenerator.Generate(DerivedB b) is not considered a valid candidate because Generator has absolutely no idea of its existence and you are calling the method through the statically typed Generator instance this (I note that the method being protected is not the issue, even if it were public the second call would fail). Why should a base class know anything about a new virtual method defined in a derived class?

In order to make this work, you either define virtual Generate(DerivedB) in the base class Generator and override it in DerivedGenerator or if that is not an option then you make everything resolve at runtime.

In your case, as Brian correctly points out, you would need to make the Generator instance dynamic too in order to allow binding the Generate call to DerivedGenerator.Generate when appropiate. Otherwise the candidates set will be limited only to those Generator knows about.

This will oblige you to significantly restructure your code as you will also need to make Generate at least internal or internal protected.

Also, I should note that making a dynamic.Generate(dynamic) call to make things work seems like a big code smell to me and you are probably abusing C#'s type system. I'd consider refactoring your code to a safer solution.

I also recommend you read Eric Lippert's fantastic series: Wizards and warriors explaining how some object hierarchies can't be well expressed with C#'s type system and how you could (but normally shouldn't) use dynamic to achieve double dispatch in C# in order to circumvent some of its limitations.

InBetween
  • 32,319
  • 3
  • 50
  • 90
  • this is an example of late binding which happens at run time because item is declared at dynamic. But the overloads are only located in the same class as the call is happening. But be sure that the binding is happening at run time otherwise the compiler would complain because no overload accepts BaseT. – Siamak S. Sep 09 '15 at 03:54
  • @SiamakS. I've edited my answer to clarify some important points. – InBetween Sep 09 '15 at 16:03