在单个继承树中进行双重分派
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.Reflection
,Intersect(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)
。
我有一个不同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.Reflection
,Intersect(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)
。