I have the following Shape hierarchy:
public abstract class Shape
{ ... }
public class Rectangle : Shape
{ ... }
public class Circle : Shape
{ ... }
public class Triangle : Shape
{ ... }
I have implemented the following functionality to determine if two shapes are intersecting. I use the following IsOverlapping
extension method, which uses dynamic
to call the appropriate overloaded IsOverlappingSpecialisation
method at runtime. I believe this is called double dispatching.
static class ShapeActions
{
public static bool IsOverlapping(this Shape shape1, Shape shape2)
{
return IsOverlappingSpecialisation(shape1 as dynamic, shape2 as dynamic);
}
private static bool IsOverlappingSpecialisation(Rectangle rect, Circle circle)
{
// Do specialised geometry
return true;
}
private static bool IsOverlappingSpecialisation(Rectangle rect, Triangle triangle)
{
// Do specialised geometry
return true;
}
This means I can do the following:
Shape rect = new Rectangle();
Shape circle = new Circle();
bool isOverlap = rect.IsOverlapping(circle);
The problem I face now, is that I will have to also implement the following in ShapeActions
for circle.IsOverlapping(rect)
to work:
private static bool IsOverlappingSpecialisation(Circle circle, Rectangle rect)
{
// The same geometry maths is used here
return IsOverlappingSpecialisation(rect, circle);
}
This is redundant (as I will need to do this for every new shape created). Is there a way I could possibly get around this? I thought of passing in a Tuple
parameter into IsOverlapping
, but I still have problems. Essentially I want overloading to occur based on unique unordered parameter sets (I know this is not possible, so looking for a workaround).
I may be overcomplicating things here, but it works...
public static class OverlapCalculator
{
private static readonly Dictionary<Tuple<Type, Type>, Delegate> Calculations = new Dictionary<Tuple<Type, Type>, Delegate>();
public static bool IsOverlapping<TShape, TOtherShape>(this TShape shape, TOtherShape otherShape)
where TShape : Shape
where TOtherShape : Shape
{
var calculation = GetCalculationDelegate<TShape, TOtherShape>();
if (calculation != null)
{
return calculation(shape, otherShape);
}
throw new InvalidOperationException(string.Format("Could not find calculation for {0} and {1}", typeof(TShape).Name, typeof(TOtherShape).Name));
}
public static void AddCalculation<TShape, TOtherShape>(Func<TShape, TOtherShape, bool> calculation)
where TShape : Shape
where TOtherShape : Shape
{
var key = new Tuple<Type, Type>(typeof(TShape), typeof(TOtherShape));
Calculations[key] = calculation;
var reverseKey = new Tuple<Type, Type>(typeof(TOtherShape), typeof(TShape));
var reverseCalculation = new Func<TOtherShape, TShape, bool>((otherShape, shape) => calculation(shape, otherShape));
Calculations[reverseKey] = reverseCalculation;
}
private static Func<TShape, TOtherShape, bool> GetCalculationDelegate<TShape, TOtherShape>()
{
var key = new Tuple<Type, Type>(typeof(TShape), typeof(TOtherShape));
Delegate calculationDelegate;
if (Calculations.TryGetValue(key, out calculationDelegate))
{
return (Func<TShape, TOtherShape, bool>) calculationDelegate;
}
return null;
}
}
This just stores delegates in a Dictionary
and tries to get a matching one when you call IsOverlapping
on a Shape
.
You use it like this:
public class Program
{
public static void Main()
{
// Add the calculation algorithm defined below.
OverlapCalculator.AddCalculation<Rectangle, Triangle>(IsOverlapping);
var rect = new Rectangle();
var triangle = new Triangle();
var circle = new Circle();
// These will work since we have a two way calculation for Rectangle and Triangle
rect.IsOverlapping(triangle);
triangle.IsOverlapping(rect);
// This will throw since we have no calculation between Circle and Triangle.
circle.IsOverlapping(triangle);
}
private static bool IsOverlapping(Rectangle rectangle, Triangle triangle)
{
// Do specialised geometry
return true;
}
}
This should be a neat and fast (no reflection) solution to your problem.
One drawback with this solution is that you have to "declare" the calculation methods using the AddCalculation
method.