views:

118

answers:

3

What a great site this is, I have lurked on here reading others questions for ages but now I have one of my own.

My workmate wrote a class very like the one below. As soon as I saw it I knew it wouldn't work but I have no explination for him why it doesn't work.

What he expected when declaring it as a ControlItem<Button> is that the Draw(Button) method would be called when using the base to call Draw(). Instead we always end up throwing the exception.

Is this a covariance issue?

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
 }
}
+2  A: 

This is because the compiler only knows for sure that the type will be a control, so it will always bind to the method with the Control argument. You need to add an explicit check in the Draw() method if you need to handle them differently:

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

Note that this is not very "generic"... but it may do the trick in your special case.

Lucero
+4  A: 

Is this a covariance issue?

No, it's a static versus dynamic dispatch issue. Static dispatch means an overloaded method call is bound to the appropriate type at compile time based on the type of the variable passed in:

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"); }
}

Dynamic dispatch means the function is bound at runtime based on the actual type of the object stored in the variable:

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"); }
}

The last print shows the difference between the two. C#, like most class-based OOP languages, only supports dynamic dispatch for the this implicit parameter (referred to as "single dispatch). In other words overridden methods are dynamically dispatched, but overloaded methods are not.

The typical solution to fake multiple dispatch in single dispatch languages is by using the visitor pattern, which would work for you here.

munificent
OK I see your point and man those answers came quick. However to follow your point through it's not the same as your example. In your case at compile time c is declared as Base but in mine at compile time T is Button so it is declared as Button MyButton.Same as yours being Derived b.
That's true, but at compile time, all it knows is the constraint: T is a Control, so it binds to the method that takes a Control. This is different from C++ templates which are instantiated before compilation.
munificent
@munificent : +1 Very well explained. Helped me too. Thanks!
Codex
+2  A: 

To build on munificent's answer: Unlike C++ templates, C# generics are not instantiated at compile time. The code generated by the C# compiler for a generic type is completely agnostic to the specializations you use in your code. The compiler spits out one piece of code that works for any substitution of type parameters that meets the constraints. It isn't until runtime, when an instance of a fully specified generic type is instantiated, that the JIT compiler will create code specific to your type parameters.

Since the code that is generated works for anything that meets the criteria of the constraints, the C# compiler treats your MyControl member as a variable of type Control (not T) since that is as much as it can infer from the constraints. Since the compiler has to emit generic code for the class, it must choose which method to call based on what it knows, and since it can't be sure whether or not MyControl will be a Button at runtime, it must choose Draw(Control).

Neil Williams
Well I don't know why but today this makes total sense to me of course it decidedes at compile time which overload to use. If it was in a class library it knows nothing about how the class implemented later in some other usage.Thanks all for the help.