这是一个多么伟大的网站,我潜伏在这里阅读其他问题多年,但现在我有一个我自己的问题。
我的同事写了一个非常类似于下面的课程。我一见到它就知道它不会起作用,但我没有为他探索为什么它不起作用。
当将它声明为ControlItem<Button>
时,他所期望的是当使用base调用Draw()时将调用Draw(Button)方法。相反,我们总是抛出异常。
这是一个协方差问题吗?
public abstract class ControlItem
{
public ControlItem()
{
}
abstract public void Draw();
}
public class ControlItem<T> : ControlItem where T : Control, new()
{
public T MyControl { get; set; }
private ControlItem()
{ }
public ControlItem(T control)
: base()
{
MyControl = control;
}
public override void Draw()
{
Draw(this.MyControl);
}
public void Draw(Control cntrl)
{
throw new NotImplementedException();
}
public void Draw(Button button)
{
//Do some work
}
}
答案 0 :(得分:4)
这是一个协方差问题吗?
不,这是静态与动态调度问题。静态分派意味着在编译时根据传入的变量的类型将重载方法调用绑定到适当的类型:
class Base { }
class Derived : Base { }
class Foo
{
void Test()
{
Base a = new Base();
Overload(a); // prints "base"
Derived b = new Derived();
Overload(b); // prints "derived"
// dispatched based on c's declared type!
Base c = new Derived();
Overload(c); // prints "base"
}
void Overload(Base obj) { Console.WriteLine("base"); }
void Overload(Derived obj) { Console.WriteLine("derived"); }
}
动态分派意味着函数在运行时根据存储在变量中的对象的实际类型进行绑定:
class Base
{
public virtual void Override() { Console.WriteLine("base"); }
}
class Derived : Base
{
public override void Override() { Console.WriteLine("derived"); }
}
class Foo
{
void Test()
{
Base a = new Base();
a.Override(); // prints "base"
Derived b = new Derived();
b.Override(); // prints "derived"
// dynamically dispatched based type of object stored in c!
Base c = new Derived();
c.Override(); // prints "derived"
}
void Overload(Base obj) { Console.WriteLine("base"); }
void Overload(Derived obj) { Console.WriteLine("derived"); }
}
最后一个印刷品显示了两者之间的差异。与大多数基于类的OOP语言一样,C#仅支持this
隐式参数的动态调度(称为“单一调度”)。换句话说,重写方法是动态调度的,但< em>重载方法不是。
在单一调度语言中伪造多个调度的典型解决方案是使用visitor pattern,这对您有用。
答案 1 :(得分:2)
这是因为编译器只知道该类型将是一个控件,因此它将始终使用Control参数绑定到该方法。如果需要以不同方式处理它们,则需要在Draw()方法中添加显式检查:
public override void Draw() {
Button btn = MyControl as Button;
if (btn != null) {
Draw(btn);
} else {
Draw(this.MyControl);
}
}
请注意,这不是非常“通用”......但在特殊情况下它可能会起作用。
答案 2 :(得分:2)
建立在慷慨的答案之上:与C ++模板不同,C# generics are not instantiated at compile time。 C#编译器为泛型类型生成的代码与您在代码中使用的特殊化完全无关。编译器吐出一段代码,用于满足约束的类型参数的任何替换。直到运行时,当实例化完全指定的泛型类型的实例时,JIT编译器才会创建特定于您的类型参数的代码。
由于生成的代码适用于满足约束条件的任何内容,因此C#编译器会将MyControl
成员视为Control
类型的变量(不是T
),因为这可以从约束中推断出来。由于编译器必须为类发出通用代码,因此它必须根据它知道的内容选择调用哪个方法,因为它无法确定MyControl
是否为Button
at运行时,必须选择Draw(Control)
。