代码之家  ›  专栏  ›  技术社区  ›  Scott

使用泛型,它声明为按钮,但被视为类内部的控件。为什么?

  •  3
  • Scott  · 技术社区  · 16 年前

    这是一个多么伟大的网站,我潜伏在这里阅读其他问题多年,但现在我有一个我自己的。

    我的同事写了一个和下面的课很像的课。我一看到它就知道它不会起作用,但我没有给他解释为什么它不起作用。

    当他宣布它为 ControlItem<Button> 是在使用基函数调用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
        }
    }
    
    3 回复  |  直到 16 年前
        1
  •  4
  •   munificent    16 年前

    这是协方差问题吗?

    不,这是静态与动态调度问题。静态调度意味着重载方法调用在编译时根据 变量 通过:

    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 隐式参数(称为“单调度”)。换言之 重写的 方法是动态调度的,但是 超载 方法不是。

    在单调度语言中伪造多个调度的典型解决方案是使用 visitor pattern 在这里对你有用。

        2
  •  2
  •   Lucero    16 年前

    这是因为编译器只知道类型将是一个控件,所以它总是用control参数绑定到方法。如果需要以不同的方式处理它们,则需要在draw()方法中添加显式签入:

    public override void Draw() {
       Button btn = MyControl as Button;
       if (btn != null) {
          Draw(btn);
       } else {
          Draw(this.MyControl);
       }
    }
    

    请注意,这不是很“通用”…但在你的特殊情况下,它可能会起作用。

        3
  •  2
  •   Neil Williams    16 年前

    建立慷慨的答案:不同于C++模板, C# generics are not instantiated at compile time . C编译器为通用类型生成的代码与您在代码中使用的专门化完全无关。编译器会生成一段代码,用于替换满足约束的类型参数。直到运行时,当完全指定的泛型类型的实例被实例化时,JIT编译器才会创建特定于类型参数的代码。

    由于生成的代码适用于任何符合约束条件的代码,因此C编译器将处理 MyControl 成员作为类型的变量 Control (不是) T )因为这是它能从约束中推断出来的。由于编译器必须为类发出泛型代码,因此它必须根据所知道的内容选择要调用的方法,并且因为它不能确定是否 我的控制 将是 Button 在运行时,它必须选择 Draw(Control) .

    推荐文章