将通用访问者应用于非通用基 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 派生的其他类型,这很好。我认为答案涉及反射,这也很好,但我更愿意有一种无需反射的方法。
如果答案涉及 Base
和 Derived
上的抽象方法也可以;我确实对 类 有足够的控制权来添加它。但在一天结束时,我需要调用一个泛型函数,用 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 中,它会调用正确的静态方法。
目前我有这样的东西:
public abstract class Base {...}
public class Derived<T> : Base {...}
class Visitor {
public static void Visit<T>(Derived<T> d) {
...
}
}
我的问题是,给定一个我知道是 Derived
实例的 Base
引用,如何使用正确的通用实例化将 Visit
函数应用于该对象?我知道答案可能涉及类型检查的动态向下转换,以确保该对象不是从 base 派生的其他类型,这很好。我认为答案涉及反射,这也很好,但我更愿意有一种无需反射的方法。
如果答案涉及 Base
和 Derived
上的抽象方法也可以;我确实对 类 有足够的控制权来添加它。但在一天结束时,我需要调用一个泛型函数,用 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 中,它会调用正确的静态方法。