单个继承树

时间:2017-04-07 07:41:14

标签: c# inheritance dynamic dispatch visitor-pattern

我有一个不同Line - 类的继承树,从抽象Line - 类开始。我希望能够将每一行与每一行相交,有时,我不知道两种运行时类型,例如我正在呼叫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

还有其他方法可以让这项工作成功吗?如果有帮助的话,我愿意为一个或多个接口切换抽象类。也许访客模式是适用的,虽然我只看到这用于不同的继承树(并且发现它非常难看)。

1 个答案:

答案 0 :(得分:0)

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

这是.NET标准库的完整示例代码(我现在返回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)