Могу ли я переопределить производные типы?

Насколько я знаю, невозможно сделать следующее в С# 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, которые касаются этого вопроса?

Ответ 1

Это невозможно на любом языке .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) получит объект типа, которого он не ожидал.

В С# было сознательное решение запретить изменение типа при перегрузке свойств, даже если у них есть только одна из пары getter/setter, потому что в противном случае было бы очень непоследовательное поведение ( "Вы имеете в виду, я могу изменить тип на одном с геттером, но не один с геттером и сеттер? Почему бы и нет?!?" - Анонимный альтернативный юниверс Newbie).

Ответ 2

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

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
    }
}

Ответ 3

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

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

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

Ответ 4

От Wikipedia:

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

В нем явно не говорится о ковариации свойств.

Ответ 5

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

Ответ 6

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

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 - это ChildProp" ), но лучше вызвать на нем виртуальный метод, который делает правильную вещь для своего типа.

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

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

Ответ 7

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

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

На языке программирования С# поддержка как возвратного типа ковариация и параметр добавлена ​​контравариантность для делегатов в версии 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

Ответ 8

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

    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>, если только это не был другой производный тип.