无访客模式的动态调度
本文关键字:动态调度 模式 访客 | 更新日期: 2023-09-27 17:49:45
>问题
我正在使用一个已经存在的库,我无法访问其源代码。此库表示 AST。
我想复制此 AST 的一部分,但重命名对过程中变量的引用。由于可以有一个AssignCommand-Object,它包含一个Expression-object,我希望能够复制每个对象都有自己的函数,所以我可以递归地调用它们。但是,由于我无法访问库的代码,因此无法添加诸如CopyAndRename(string prefix)
之类的方法。
因此,我的方法是创建一个具有多个重载的单个函数Rename
。因此,我将有一个家庭功能如下:
public static Command Rename(Command cmd, string prefix)
public static AssignCommand Rename(AssignCommand cmd, string prefix)
public static AdditionExpressionRename(AdditionExpression expr, string prefix)
....
函数现在由一个List<Command>
组成,其中AssignCommand
是Command
的子类。我假设我可以将Command
传递给Rename
函数,运行时会找到最具体的函数。但是,情况并非如此,所有命令都传递给Command Rename(Command cmd, string prefix)
。为什么会这样呢?有没有办法在不使用丑陋的is
-操作的情况下将调用委托给正确的函数?
最小示例
我已将此问题分解为以下 NUnit-Testcode
using NUnit.Framework;
public class TopClass{
public int retVal;
}
public class SubClassA : TopClass{ }
[TestFixture]
public class ThrowawayTest {
private TopClass Foo (TopClass x) {
x.retVal = 1;
return x;
}
private SubClassA Foo (SubClassA x) {
x.retVal = 2;
return x;
}
[Test]
public void OverloadTest(){
TopClass t = new TopClass();
TopClass t1 = new SubClassA();
SubClassA s1 = new SubClassA();
t = Foo (t);
t1 = Foo (t1);
s1 = Foo (s1);
Assert.AreEqual(1, t.retVal);
Assert.AreEqual(2, s1.retVal);
Assert.AreEqual(2, t1.retVal);
}
}
所以我的问题归结为:"如何在不诉诸is
检查的情况下以优雅、多态、面向对象的方式修复上面的测试?
扩展方法
我也尝试使用扩展方法,如下所示。这并没有解决问题,因为它们只是上述方法的语法糖:
using NUnit.Framework;
using ExtensionMethods;
public class TopClass{
public int retVal;
}
public class SubClassA : TopClass{ }
[TestFixture]
public class ThrowawayTest {
private TopClass Foo (TopClass x) {
x.retVal = 1;
return x;
}
private SubClassA Foo (SubClassA x) {
x.retVal = 2;
return x;
}
[Test]
public void OverloadTest(){
TopClass t = new TopClass();
TopClass t1 = new SubClassA();
SubClassA s1 = new SubClassA();
t.Foo(); s1.Foo(); t1.Foo();
Assert.AreEqual(1, t.retVal);
Assert.AreEqual(2, s1.retVal);
Assert.AreEqual(2, t1.retVal);
}
}
namespace ExtensionMethods{
public static class Extensions {
public static void Foo (this TopClass x) {
x.retVal = 1;
}
public static void Foo (this SubClassA x) {
x.retVal = 2;
}
}
}
与凯文的回答类似,我会考虑利用dynamic
关键字。我只提另外两种方法。
现在,您实际上不需要访问源代码,您只需要访问类型本身,即程序集。只要类型public
(不是private
或internal
(,这些应该可以工作:
动态访客
这使用与传统访客模式类似的方法。
创建一个访问者对象,每个子类型(结束类型,而不是中间类或基类,如Command
(使用一种方法,接收外部对象作为参数。
然后要调用它,在一个特定的对象上,你在编译时不知道它的确切类型,只需像这样执行访问者:
visitor.Visit((dynamic)target);
您还可以在访问者本身中处理递归,用于具有要访问的子表达式的类型。
处理程序词典
现在,如果您只想处理少数类型,而不是所有类型,则只需创建按Type
索引的处理程序Dictionary
可能会更简单。这样,您可以检查字典是否具有确切类型的处理程序,如果有,则调用它。要么通过标准调用,这可能会强制你在处理程序中强制转换,要么通过 DLR 调用,这不会,但会对性能造成一些影响(。
我不确定 Mono 是否支持它,但您可以通过在 C# 4.0 中非常具体地使用泛型和 dynamic
关键字来完成您想要的东西。 您尝试做的是创建一个新的虚拟槽,但语义略有不同(C# 虚拟函数不是协变的(。 dynamic
所做的是将函数重载解析推送到运行时,就像虚拟函数一样(尽管效率要低得多(。 扩展方法和静态函数都具有编译时重载分辨率,因此变量的静态类型是使用的,这是您正在解决的问题。
public class FooBase
{
public int RetVal { get; set; }
}
public class Bar : FooBase {}
设置动态访客。
public class RetValDynamicVisitor
{
public const int FooVal = 1;
public const int BarVal = 2;
public T Visit<T>(T inputObj) where T : class
{
// Force dynamic type of inputObj
dynamic @dynamic = inputObj;
// SetRetVal is now bound at runtime, not at compile time
return SetRetVal(@dynamic);
}
private FooBase SetRetVal(FooBase fooBase)
{
fooBase.RetVal = FooVal;
return fooBase;
}
private Bar SetRetVal(Bar bar)
{
bar.RetVal = BarVal;
return bar;
}
}
特别令人感兴趣的是Visit<T>
Visit(new Bar())
inputObj, @dynamic
的类型。
public class RetValDynamicVisitorTests
{
private readonly RetValDynamicVisitor _sut = new RetValDynamicVisitor();
[Fact]
public void VisitTest()
{
FooBase fooBase = _sut.Visit(new FooBase());
FooBase barAsFooBase = _sut.Visit(new Bar() as FooBase);
Bar bar = _sut.Visit(new Bar());
Assert.Equal(RetValDynamicVisitor.FooVal, fooBase.RetVal);
Assert.Equal(RetValDynamicVisitor.BarVal, barAsFooBase.RetVal);
Assert.Equal(RetValDynamicVisitor.BarVal, bar.RetVal);
}
}
我希望这在 Mono 中是可行的!
这是没有动态的版本,动态版本太慢(第一次调用(:
public static class Visitor
{
/// <summary>
/// Create <see cref="IActionVisitor{TBase}"/>.
/// </summary>
/// <typeparam name="TBase">Base type.</typeparam>
/// <returns>New instance of <see cref="IActionVisitor{TBase}"/>.</returns>
public static IActionVisitor<TBase> For<TBase>()
where TBase : class
{
return new ActionVisitor<TBase>();
}
private sealed class ActionVisitor<TBase> : IActionVisitor<TBase>
where TBase : class
{
private readonly Dictionary<Type, Action<TBase>> _repository =
new Dictionary<Type, Action<TBase>>();
public void Register<T>(Action<T> action)
where T : TBase
{
_repository[typeof(T)] = x => action((T)x);
}
public void Visit<T>(T value)
where T : TBase
{
Action<TBase> action = _repository[value.GetType()];
action(value);
}
}
}
接口声明:
public interface IActionVisitor<in TBase>
where TBase : class
{
void Register<T>(Action<T> action)
where T : TBase;
void Visit<T>(T value)
where T : TBase;
}
用法:
IActionVisitor<Letter> visitor = Visitor.For<Letter>();
visitor.Register<A>(x => Console.WriteLine(x.GetType().Name));
visitor.Register<B>(x => Console.WriteLine(x.GetType().Name));
Letter a = new A();
Letter b = new B();
visitor.Visit(a);
visitor.Visit(b);
控制台输出:A,B,请查看更多详细信息。