Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to improve this method using polymorphism+overloading so as to reduce IS (type check)?

For example

BaseClass MyBase()
{
    public int Add(BaseClass next)
    {
        if (this is InheritedA && next is InheritedA)
            return 1;
        else if (this is InheritedA && next is InheritedB)
            return 2;
        else if (this is InheritedB && next is InheritedA)
            return 3;
        else if (this is InheritedB && next is InheritedB)
            return 4;      
     }
}

where InheritedA, and InheritedB are its inherited classes. In fact, there are more Inherited classes, and the Add returns different results based on the order and types of its operand.

I am thinking of rewriting it using Polymorphism and overloading, however, it becomes rather complicated, I have to introduce an helper method to resolve the type of either end.

e.g.

InheritedA myA()
{
    public override int Add(BaseClass next)
    {
        return next.AddTo(this);
    }
}

Now I have to put AddTo into BaseClass, and override it in inherited class as well.

InheritedA myA()
{
    public override int AddTo(InheritedA next) { return 1; }
    public override int AddTo(InheritedB next) { return 3; }
}

BaseClass myBase()
{
    public abstract int Add(BaseClass next);
    public abstract int AddTo(InheritedA next);
    public abstract int AddTo(InheritedB next);
}

Is there a better way of doing it?

like image 227
colinfang Avatar asked Mar 27 '12 15:03

colinfang


2 Answers

The pattern you are implementing is called double virtual dispatch.

A single virtual dispatch chooses which method to call on the basis of the run-time type of the receiver and the compile time type of the arguments. This is a traditional virtual dispatch:

abstract class Animal {}
class Tiger : Animal {}
class Giraffe : Animal {} 
class B
{
    public virtual void M(Tiger x) {}
    public virtual void M(Animal x) {}
}
class D : B
{
    public override void M(Tiger x) {}
    public override void M(Animal x) {}
}
...
B b = whatever;
Animal a = new Tiger();
b.M(a);

Which method is called? B.M(Tiger) and D.M(Tiger) are not chosen; we reject them based on the compile time type of the argument, which is Animal. But we choose whether to call B.M(Animal) or D.M(Animal) at runtime based on whether whatever is new B() or new D().

Double virtual dispatch chooses which method to call based on the runtime types of two things. If C# supported double virtual dispatch, which it does not, then the runtime dispatch would go to B.M(Tiger) or D.M(Tiger) even though the compile-time type of the argument is Animal.

C# 4 does however support dynamic dispatch. If you say

dynamic b = whatever;
dynamic a = new Tiger();
b.M(a);

Then the analysis of M will be done entirely at runtime using the runtime types of b and a. This is significantly slower, but it does work.

Alternatively, if you want to do double virtual dispatch and get as much analysis done at compile time as possible then the standard way to do that is to implement the Visitor Pattern, which you can look up on the internet easily.

like image 84
Eric Lippert Avatar answered Sep 21 '22 22:09

Eric Lippert


As was suggested in the comments, if you are able to assign a constant value to each derived, then you can build a much cleaner implementation than I'm describing here by just having a virtual property named Value or similar that is used to for the addition.

Assuming that isn't an option, you may wish to consider pre-computing the results at the base class level to describe the values that you're assigning for each combination. This can break down and become error prone and tedious as the set of classes grows, so I would suggest only considering this if you expect a very small set to maintain.

In my rudimentary example, I used a dictionary to hold the set and hard-coded the combinations. From your comment, it appears that none of the basic rules of arithmetic apply, so I've left them out as constraints here. If the result value has no actual meaning and you're just incrementing it, you could consider building the result set using reflection to pull the derived classes and considering each combination.

public class BaseClass
{
  private static readonly Dictionary<int, int> addResults = new Dictionary<int, int>();

  static BaseClass()
  {
    addResults.Add(CreateKey(typeof(ChildA), typeof(ChildA)), 1);
    addResults.Add(CreateKey(typeof(ChildA), typeof(ChildB)), 2);
    addResults.Add(CreateKey(typeof(ChildB), typeof(ChildA)), 3);
    addResults.Add(CreateKey(typeof(ChildB), typeof(ChildB)), 4);
  }

  public static int CreateKey(Type a, Type b)
  {
    return (String.Concat(a.Name, b.Name).GetHashCode());
  }

  public int Add(BaseClass next)
  {
    var result = default(int);

    if (!addResults.TryGetValue(CreateKey(this.GetType(), next.GetType()), out result))
    {
      throw new ArgumentOutOfRangeException("Unknown operand combination");
    }

    return result;
  }
}

public class ChildA : BaseClass {}
public class ChildB : BaseClass {}
like image 41
Jesse Squire Avatar answered Sep 20 '22 22:09

Jesse Squire