Используя дженерики, он объявлен как Button, но рассматривается как внутренний элемент управления класса. Зачем? - PullRequest
3 голосов
/ 08 апреля 2009

Какой замечательный сайт, я скрывался здесь, читая другие вопросы целую вечность, но теперь у меня есть один из моих собственных.

Мой напарник написал класс, очень похожий на приведенный ниже. Как только я увидел это, я понял, что это не сработает, но у меня нет объяснений, почему это не работает.

Что он ожидал, объявив его как ControlItem<Button>, так это то, что метод Draw (Button) будет вызываться при использовании базы для вызова Draw (). Вместо этого мы всегда выкидываем исключение.

Это проблема ковариации?

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 ]

4 голосов
/ 08 апреля 2009

Это проблема ковариации?

Нет, это проблема статической или динамической отправки. Статическая диспетчеризация означает, что перегруженный вызов метода привязан к соответствующему типу во время компиляции на основе типа переменной , переданной в:

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

Последний отпечаток показывает разницу между ними. C #, как и большинство языков ООП на основе классов, поддерживает только динамическую диспетчеризацию для неявного параметра this (называемого «одиночной диспетчеризацией»). Другими словами, переопределенные методы отправляются динамически, но перегружены методов нет.

Типичное решение для фальсификации множественных отправок на языках с одной отправкой - использование шаблона посетителя , который подойдет вам здесь.

2 голосов
/ 08 апреля 2009

Основываясь на ответе munificent: в отличие от шаблонов C ++, генерики C # не создаются во время компиляции . Код, сгенерированный компилятором C # для универсального типа, полностью не зависит от специализаций, которые вы используете в своем коде. Компилятор выплевывает один кусок кода, который работает для любой замены параметров типа, которая удовлетворяет ограничениям. Только во время выполнения, когда создается экземпляр полностью указанного универсального типа, JIT-компилятор создает код, специфичный для параметров вашего типа.

Поскольку сгенерированный код работает для всего, что соответствует критериям ограничений, компилятор C # обрабатывает ваш MyControl член как переменную типа Control (не T), поскольку это столько же, сколько и можно сделать вывод из ограничений. Поскольку компилятор должен генерировать универсальный код для класса, он должен выбирать, какой метод вызывать, основываясь на том, что он знает, и поскольку он не может быть уверен, будет ли MyControl Button во время выполнения, он должен выберите Draw(Control).

2 голосов
/ 08 апреля 2009

Это потому, что компилятор точно знает, что тип будет элементом управления, поэтому он всегда будет привязываться к методу с аргументом Control. Вам нужно добавить явную проверку в методе Draw (), если вам нужно обрабатывать их по-разному:

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

Обратите внимание, что это не очень "универсальный" ... но он может помочь в вашем особом случае.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...