我刚刚遇到了同样的问题。选项 2(使用扩展方法)工作得很好,直到您需要下限为虚拟的方法(因此根据对象的动态类型进行调度)。如果您需要,这里有一个使用选项 3 的可行解决方案(界面变化,加上众所周知的访问者模式)。
为了达到相当于
public class A<T> // an argument to the generic method
{
}
public class B<S>
{
public virtual R Fun<T>(A<T> arg) where S : T // illegal in C#/CLR
{
...
}
}
public class C<S> : B<S>
{
public override R Fun<T>(A<T> arg)
{
}
}
您执行以下操作。首先,您为要执行的操作定义一个接口(我们将在这里使用访问者模式,因此必须为每种类型覆盖一个单独的方法Fun
):
public interface IFun<in T>
{
R Fun<S>(B<S> self) where S : T;
R Fun<S>(C<S> self) where S : T;
}
请注意,泛型参数T
仅用作约束,因此接口可以相对于它是逆变的。我们现在使用它,让B
操作C
被“访问”:
public class B<S>
{
public virtual R Perform(IFun<S> fun)
// contravariant, any IFun<T> with S : T will be accepted
{
return fun.Fun(this);
}
}
public class C<S> : B<S>
{
public override R Perform(IFun<S> fun)
{
return fun.Fun(this);
}
}
为了使用参数实际执行操作A<T>
,您将其包装在实现接口的结构/类中:
public struct TheFun<T> : IFun<T>
{
public A<T> arg;
R IFun<T>.Fun<S>(B<S> self)
{
... body of B<S>.Fun(A<T> arg) ...
}
R IFun<T>.Fun<S>(C<S> self)
{
... body of C<S>.Fun(A<T> arg) ...
}
}
最后,您引入一个扩展方法,如选项 2 中所示:
public static class Extensions
{
public static R Fun<S,T>(this B<S> self, A<T> arg) where S : T
{
return self.Perform(new TheFun<T> { arg = arg });
}
}
完毕。它可以工作,无需进行一次强制转换或类型检查。主要缺点是:
- 它非常复杂(尽管代码大小比预期的要长,只是一个常数因素)并且可能会让阅读您的代码的人讨厌您
- 实现已经从
B
和C
移到TheFun
,因此任何必需的成员B
都C
必须在那里可以访问