没有访客模式的动态调度

Ale*_*ert 11 c# polymorphism mono overloading dynamic-dispatch

问题

我正在使用已经存在的库,以及我无法访问的源代码.该库代表AST.

我想复制此AST的部分内容,但重命名过程中对变量的引用.由于可以有一个包含Expression对象的AssignCommand-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)
....
Run Code Online (Sandbox Code Playgroud)

函数现在由a组成List<Command>,where AssignCommand是其子类Command.我假设我可以只传递CommandRename-function,运行时会找到最具体的一个.但是,情况并非如此,所有命令都会传递给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);
    }
}
Run Code Online (Sandbox Code Playgroud)

所以我的问题归结为:"如何在不使用is-checks的情况下以优雅,多态,面向对象的方式修复上述测试?"

扩展方法

我也尝试过如下使用扩展方法.这并没有解决问题,因为它们只是上述方法的语法糖:

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Pab*_*meo 7

与Kevin的答案相似,我会考虑利用dynamic关键字.我只想提到另外两种方法.

现在,您实际上并不需要访问源代码,您只需要访问类型本身,即组件.只要类型是public(不是privateinternal)这些应该工作:

动态访客

这个使用与传统访客模式类似的方法.

创建一个访问者对象,每个子类型有一个方法(结束类型,不是中间类或基类,如Command),接收外部对象作为参数.

然后调用它,在编译时你不知道确切类型的特定对象上,只需像这样执行访问者:

visitor.Visit((dynamic)target);
Run Code Online (Sandbox Code Playgroud)

对于包含要访问的子表达式的类型,您还可以在访问者本身内处理递归.

处理程序字典

现在,如果您只想处理一些类型,而不是所有类型,那么您可以更简单地创建一个Dictionary索引的处理程序Type.通过这种方式,您可以检查字典是否具有确切类型的处理程序,如果是,则调用它.通过标准调用,可能会强制您在您的处理程序中进行强制转换,或者通过DLR调用,这将不会产生一点性能损失.