Siamak S.
Siamak S.

Reputation: 81

C# late binding method overloads does not work when overload is defined in a derived class

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);
    }
}

Upvotes: 5

Views: 614

Answers (2)

InBetween
InBetween

Reputation: 32760

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.

Upvotes: 2

Brian Rudolph
Brian Rudolph

Reputation: 6318

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);
    }
}

Upvotes: 3

Related Questions