将通用访问者应用于非通用基 class 的通用派生 class

Apply generic visitor to generic derived class of non-generic base class

目前我有这样的东西:

public abstract class Base {...}
public class Derived<T> : Base {...}

class Visitor {
    public static void Visit<T>(Derived<T> d) {
        ...
    }
}

我的问题是,给定一个我知道是 Derived 实例的 Base 引用,如何使用正确的通用实例化将 Visit 函数应用于该对象?我知道答案可能涉及类型检查的动态向下转换,以确保该对象不是从 base 派生的其他类型,这很好。我认为答案涉及反射,这也很好,但我更愿意有一种无需反射的方法。

如果答案涉及 BaseDerived 上的抽象方法也可以;我确实对 类 有足够的控制权来添加它。但在一天结束时,我需要调用一个泛型函数,用 Derived 类型的 T 正确实例化。

抱歉,如果这是一个简单的问题;我来自 C++ 背景,我的直觉是使用 CRTP 或其他类似的东西,这在 C# 中是不可能的。

编辑:

这是我需要能够执行的操作的示例:

Base GetSomeDerivedInstance() { ...; return new Derived<...>(); }
var b = GetSomeDerivedInstance();

// This is the line that needs to work, though it doesn't necessarily
// need to have this exact call signature. The only requirement is that
// the instantiated generic is invoked correctly.
Visitor.Visit(b);

(为清晰起见进行了编辑)

以下将使用名为 "anyvalue" 的变量,其类型仅在 运行 时已知。然后我们将根据任意值的类型创建您的 Derived class 的实例。一旦我们有了那个实例,我们就可以使用反射来获得正确的 Visit 方法。

var anyvalue = 5; // This value could have come from anywhere. 
...    
var derivedType = typeof (Derived<>).MakeGenericType(anyvalue.GetType());
var dvalue = Activator.CreateInstance(derivedType);
var method = typeof(Visitor).GetMethod("Visit");
var genericMethod = method.MakeGenericMethod(new[] { anyvalue.GetType() });
genericMethod.Invoke(null, new [] { dvalue });

有点令人困惑的是,这是一个骨架示例,除了获取 运行 时间类型之外,您不会将原始值用于任何其他用途。在现实世界的实现中,我假设构造函数将使用该值来设置 Derived 实例中的内部状态。这里没有涉及,因为这不是所问问题的一部分。

更新:

我想这会如你所愿。请注意,我创建了 itemarray,这样我们就有了一些 运行 时间值。它们必须在某处创建。因此,无论它们是作为 object[] 传递还是以其他方式提供,它们都必须在某处使用类型说明符构造。此外,这假设 Derived<> 是唯一的派生 class。否则,这不是安全代码。

var itemarray = new Base[] { new Derived<int>(), new Derived<string>() };

foreach (var baseObject in itemarray)
{
    var derivedType = baseObject.GetType();
    var visitMethod = typeof(Visitor)
        .GetMethod("Visit")
        .MakeGenericMethod(derivedType.GetGenericArguments());
    visitMethod.Invoke(null, new[] { baseObject });
}

Accept 方法似乎更易于管理。我的目标是在不对您的方法做出判断的情况下回答您提出的问题。我需要多次使用这种方法。我大约 9 年前写了一篇 entity framework。我真的很难完全按照你想做的去做。我创建了非泛型的基础 classes,这样我就可以共享基本功能,而不管泛型类型如何。事实证明这具有挑战性。我不确定我现在是否会以同样的方式来做。我可能会像您一样调查一些模式。

您应该可以执行以下操作:

Base foo = new Derived<int>();

var method = typeof(Visitor).GetMethod("Visit", BindingFlags.Public | BindingFlags.Static);
method.MakeGenericMethod(foo.GetType().GenericTypeArguments.First()).Invoke(null, new[] {foo});

也许你的意思是这样的?

public class Derived<T>
{
}

public abstract class Derivable<T>
{
    public Derived<T> CreateDerived()
    {
        return new Derived<T>();
    }
}

public class Foo : Derivable<Foo>
{
}

class Visitor
{
    public static void Visit<T>(Derived<T> obj)
    {
        Console.Out.WriteLine("Called!");
    }
}

void Main()
{
    var obj = new Foo();
    var derived = obj.CreateDerived();
    Visitor.Visit(derived);
}

如果 Derived<T> 的创建是 T 特定的,那么您可以将 CreateDerived 方法抽象化并为每个 T 实现它。或者,如果您不想将其作为基础 class.

,请改用 IDerivable<T> 接口

在我看来,涉及双重调度和更多 类 的答案将优于使用反射来完成继承应该为您做的事情。

通常这意味着在可访问的 class 上定义一个 'accept' 方法,它只是从访问者调用正确的 Visit 方法。

class Base
{
    public virtual void Accept(Visitor visitor)
    {
        visitor.Visit(this); // This calls the Base overload.
    }
}

class Derived<T> : Base
{
    public override void Accept(Visitor visitor)
    {
        visitor.Visit(this); // this calls the Derived<T> overload.
    }
}

public class Visitor
{
    public void Visit(Base @base)
    {
        ...
    }

    public void Visit<T>(Derived<T> derived)
    {
        ...
    }
}

然后你可以做你在问题中提到的,稍作修改:

Base b = createDerived();
b.Accept(new Visitor());

如果您的访问方法是静态的 class,无论出于何种原因您都无法更改,您总是可以将其包装到一个虚拟实例访问者 class 中,它会调用正确的静态方法。