在单个继承树中进行双重分派

Double dispatch inside a single inheritance tree

我有一个不同Line-class的继承树,从抽象Line-class开始。我希望能够将每一行与另一行相交,有时,我不知道这两种运行时类型,例如我打电话给 Line.Intersect(Line)(所以我需要双重调度)。这将始终调用覆盖的 Intersect 方法的最抽象重载,例如Circle.Intersect(Line) 而不是 Circle.Intersect(actualType)。下面是一些示例代码:

class Program
{
  static void Main(string[] args)
  {
    Line straightLine = new StraightLine();
    Line circle = new Circle();

    // Will print: "Circle intersecting a line."
    // But should print: "Circle intersecting a straight line."
    circle.Intersect(straightLine);

    Console.ReadLine();
  }
}


abstract class Line
{
  public abstract void Intersect(Line line);

  public abstract void Intersect(StraightLine straightLine);

  public abstract void Intersect(Circle circle);
}


class StraightLine : Line
{
  public override void Intersect(Line line)
  {
    Console.WriteLine("Straigth line intersecting a line.");
  }

  public override void Intersect(StraightLine straightLine)
  {
    Console.WriteLine("Straight line intersecting a straight line.");
  }

  public override void Intersect(Circle circle)
  {
    Console.WriteLine("Straight line intersecting a circle.");
  }
}


class Circle : Line
{
  public override void Intersect(Line line)
  {
    Console.WriteLine("Circle intersecting a line.");
  }

  public override void Intersect(Circle circle)
  {
    Console.WriteLine("Circle intersecting a circle.");
  }

  public override void Intersect(StraightLine straightLine)
  {
    Console.WriteLine("Circle intersecting a straight line.");
  }
}

一种可能的解决方法是使用 dynamic,我目前就是这样做的。但是,我想迁移到 .NET 标准库,其中 dynamic 是不允许的。

还有其他方法可以实现吗?如果有帮助,我愿意为一个或多个接口切换抽象 class。也许访问者模式是适用的,虽然我只看到它用于不同的继承树(并且发现它非常丑陋)。

可以使用反射模拟双重分派。针对 .NET Standard 1.1 并使用 Nuget-Package System.ReflectionIntersect(Line line)-方法不需要是抽象的或虚拟的,但只需实现一次。

这是 .NET Standard 库的完整示例代码(我现在 return a string 而不是使用 Console.WriteLine(),因为后者在 .NET Standard 中不可用):

using System.Reflection;

namespace IntersectLibrary
{
  public abstract class Line
  {
    public string Intersect(Line line)
    {
      var method = this.GetType().GetRuntimeMethod(nameof(Intersect), new[] { line.GetType() });
      return (string)method.Invoke(this, new[] { line });
    }

    public abstract string Intersect(StraightLine straightLine);

    public abstract string Intersect(Circle circle);
  }


  public class StraightLine : Circle
  {
    public override string Intersect(StraightLine straightLine)
    {
      return "Straight line intersecting a straight line.";
    }

    public override string Intersect(Circle circle)
    {
      return "Straight line intersecting a circle.";
    }
  }


  public class Circle : Line
  {
    public override string Intersect(Circle circle)
    {
      return "Circle intersecting a circle.";
    }

    public override string Intersect(StraightLine straightLine)
    {
      return "Circle intersecting a straight line.";
    }
  }
}

请注意,针对 .NET Framework 时,System.Reflection 提供不同的方法,需要修改代码。

在控制台应用程序中,将发生以下情况:

using System;
using IntersectLibrary;

namespace ConsoleApplication
{
  class Program
  {
    static void Main(string[] args)
    {
      Line straightLine = new StraightLine();
      Line circle = new Circle();
      Circle circle2 = new Circle();

      // Calls "Line.Intersect(Line)", and correctly
      // prints "Circle intersecting a straight line.".
      Console.WriteLine(circle.Intersect(straightLine));

      // Also calls "Line.Intersect(Line)",
      // since the argument's compile-time type is "Line".
      Console.WriteLine(circle2.Intersect(straightLine));

      // Calls "Line.Intersect(Circle)",
      // since the argument's compile-time type is "Circle".
      // At runtime, the call will be resolved to
      // "StraightLine.Intersect(Circle)" via single dispatch.
      Console.WriteLine(straightLine.Intersect(circle2));

      Console.ReadLine();
    }
  }
}

如果您现在有一个编译时类型的对象 Line 和一个具体类型的对象(例如 Circle),最好调用 Line.Intersect(Circle),因为这样不会不需要(较慢的)反射来解析方法调用。但是,Circle.Intersect(Line) 也可以工作,最重要的是,现在总是可以调用 Line.Intersect(Line)