简体   繁体   中英

Overloading methods based on unordered parameter sets

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM