Могу ли я переопределить с производными типами? - PullRequest
38 голосов
/ 01 октября 2008

Насколько я знаю, в C # 2.0

невозможно сделать следующее
public class Father
{
    public virtual Father SomePropertyName
    {
        get
        {
            return this;
        }
    }
}

public class Child : Father
{
    public override Child SomePropertyName
    {
        get
        {
            return this;
        }
    }
}

Я обошел проблему, создав свойство в производном классе как «новое», но, конечно, это не полиморфно.

public new Child SomePropertyName

Есть ли решение в 2.0? Как насчет каких-либо функций в 3.5, которые решают эту проблему?

Ответы [ 8 ]

41 голосов
/ 01 октября 2008

Вы можете повторно объявить (новое), но вы не можете повторно объявить и переопределить одновременно (с тем же именем). Одним из вариантов является использование защищенного метода, чтобы скрыть детали - это позволяет и полиморфизм, и скрытие одновременно:

public class Father
{
    public Father SomePropertyName
    {
        get {
            return SomePropertyImpl();
        }
    }
    protected virtual Father SomePropertyImpl()
    {
        // base-class version
    }
}

public class Child : Father
{
    public new Child SomePropertyName
    {
        get
        { // since we know our local SomePropertyImpl actually returns a Child
            return (Child)SomePropertyImpl();
        }
    }
    protected override Father SomePropertyImpl()
    {
        // do something different, might return a Child
        // but typed as Father for the return
    }
}
25 голосов
/ 01 октября 2008

Это невозможно на любом языке .NET из-за проблем безопасности типов. В типобезопасных языках вы должны предоставить ковариацию для возвращаемых значений и противоположность для параметров. Возьми этот код:

class B {
    S Get();
    Set(S);
}
class D : B {
    T Get();
    Set(T);
}

Для методов Get ковариация означает, что T должен быть либо S, либо типом, полученным из S. В противном случае, если у вас есть ссылка на объект типа D, хранящийся в переменной с типом B, при вызове B.Get() вы не получите объект, представляемый в виде S, что нарушает систему типов .

Для методов Set, контравариантность означает, что T должен быть либо S, либо типом, из которого S происходит. В противном случае, если у вас была ссылка на объект типа D, хранящийся в переменной типа B, когда вы вызывали B.Set(X), где X был типа S, но не типа T, D::Set(T) получит объект типа, которого он не ожидал.

В C # было сознательное решение запретить изменение типа при перегрузке свойств, даже если у них есть только одна из пары геттер / установщик, потому что иначе это могло бы иметь очень противоречивое поведение ( " может изменить тип на тот, у которого есть получатель, но не тот, у которого есть и получатель, и установщик? Почему бы и нет?!? * - Anonymous Alternate Universe Newbie).

11 голосов
/ 01 октября 2008

Нет, но вы можете использовать дженерики в 2 и выше:

public class MyClass<T> where T: Person
{
    public virtual T SomePropertyName
    {
        get
        {
            return  ...;
        }
    }
}

Тогда Отец и Дитя являются родовыми версиями одного и того же класса

7 голосов
/ 01 октября 2008

Из Википедия :

В языке программирования C # поддерживается оба типа возврата ковариация и параметр добавлена ​​контрвариантность для делегатов в версии 2.0 языка. Ни ковариация, ни контравариантность поддерживаются для переопределения метода.

Хотя это явно не говорит о ковариации свойств.

2 голосов
/ 01 октября 2008

Вы можете создать общий интерфейс для отца и ребенка и вернуть тип этого интерфейса.

1 голос
/ 10 октября 2008

Это самое близкое, что я мог прийти (пока):

    public sealed class JustFather : Father<JustFather> {}

    public class Father<T> where T : Father<T>
    { public virtual T SomePropertyName
        { get { return (T) this; }
        }
    }

    public class Child : Father<Child>
    { public override Child SomePropertyName
        { get { return  this; }
        }
    }

Без класса JustFather вы не можете создать экземпляр Father<T>, если это не какой-то другой производный тип.

1 голос
/ 01 октября 2008

Нет. C # не поддерживает эту идею (это называется "тип возврата ковариация ").

Из Википедии:

На языке программирования C # поддержка обоих типов возврата ковариация и параметр добавлена ​​контрвариантность для делегатов в версии 2.0 языка. Ни ковариация, ни контравариантность поддерживаются для переопределения метода.

Вы можете повторно объявить (новый), но вы не может повторно объявить и переопределить на в то же время (с тем же именем). Один вариант заключается в использовании защищенного метода для скрыть детали - это позволяет как полиморфизм и сокрытие при этом время:

Лучшее решение - использовать дженерики:

public class MyClass<T> where T: Person
{
   public virtual T SomePropertyNameA
   {        
      get { return  ...; }    
   }
}//Then the Father and Child are generic versions of the same class
1 голос
/ 01 октября 2008

Нет. C # не поддерживает эту идею (она называется «ковариация возвращаемого типа»). Однако вы можете сделать это:

public class FatherProp
{
}

public class ChildProp: FatherProp
{
}


public class Father
{
    public virtual FatherProp SomePropertyName
    {
        get
        {
            return new FatherProp();
        }
    }
}


public class Child : Father
{
    public override FatherProp SomePropertyName
    {
        get
        {
            // override to return a derived type instead
            return new ChildProp();
        }
    }
}

т.е. используйте контракт, определенный базовым классом, но возвращайте производный тип. Я сделал более подробный пример, чтобы прояснить этот момент - возврат «this» снова ничего не изменит.

Можно (хотя и беспорядочно) проверить возвращаемый объект на предмет его фактического типа (то есть "если someObject is ChildProp"), но лучше вызвать для него виртуальный метод, который делает правильную вещь для своего типа.

Виртуальный метод базового класса (в данном случае виртуальное свойство) не только имеет реализацию, но и определяет контракт: дочерний класс может предоставлять другую реализацию SomePropertyName, если он соответствует этому контракту (т.е. SomePropertyName возвращает объект типа "FatherProp"). Возвращение объекта типа «ChildProp», производного от «FatherProp», соответствует этому контракту. Но вы не можете изменить контракт в "Child" - этот контракт применяется ко всем классам, происходящим от "Father".

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

...