Переопределение абстрактного свойства с производным возвращаемым типом в С#

У меня четыре класса. Запрос, производный запрос, обработчик, производный обработчик. Класс Handler имеет свойство со следующим объявлением:

public abstract Request request { get; set; }

DerivedHandler необходимо переопределить это свойство, чтобы вместо этого он возвращал DerivedRequest:

public override DerivedRequest request { get; set; }

У кого-нибудь есть идеи о том, как заставить это работать?


person Trevor    schedule 14.06.2011    source источник
comment
Это не совсем хороший ООП, так как нарушает интерфейс. Некоторые типы операций установки (с непроизводными values) будут вызывать неожиданное исключение.   -  person recursive    schedule 15.06.2011
comment
В этом случае, я полагаю, мне не понадобится сеттер. Я мог бы создать частную собственность и установить ее в конструкторе. Это позаботится об исключениях операций сеттера, да?   -  person Trevor    schedule 15.06.2011
comment
В этом случае вам не нужно будет переопределять свойство. Просто пусть конструктор принимает только DerivedRequest.   -  person recursive    schedule 15.06.2011


Ответы (5)


Это не очень хороший способ структурировать вещи. Выполните одно из следующих

1) Просто не меняйте возвращаемый тип и обычно переопределяйте его в подклассе. В DerivedHandler вы можете вернуть экземпляр DerivedRequest, используя сигнатуру базового класса Request. Любой клиентский код, использующий это, может при желании преобразовать его в DerivedRequest.

2) Вместо этого используйте дженерики, если они не должны быть полиморфными.

public abstract class HandlerBase<T> where T: Request
{
    public abstract T Request {get;set;}
}

public class Handler: HandlerBase<Request>()

public class DerivedHandler: HandlerBase<DerivedRequest>()
person Jamie Treworgy    schedule 14.06.2011
comment
Если есть несколько случаев, например несколько свойств, которые я хочу переопределить таким образом, чтобы они использовали производные классы, тогда мне нужно сделать что-то вроде class HandlerBase<A,B,C,..> where A:classA, B:classB,...? - person Javidan; 06.02.2017

В языке C# вам не разрешено изменять сигнатуру унаследованного метода, если только вы не замените его другим методом с таким же именем. Этот метод называется «скрытие членов» или «затенение».

Если вы используете .NET 2.0 или более позднюю версию, вы можете решить эту проблему, превратив возвращаемый тип свойства Request в параметр универсального типа класса Handler. Затем класс DerivedHandler укажет класс DerivedRequest в качестве аргумента для этого параметра типа.

Вот пример:

// Handler.cs
public class Handler<TRequest> where TRequest : Request
{
    public TRequest Request { get; set; }
}

// DerivedHandler.cs
public class DerivedHandler : Handler<DerivedRequest>
{
}
person Enrico Campidoglio    schedule 14.06.2011
comment
Вы, конечно, можете это сделать; ничто в ООП не запрещает вам делать это до тех пор, пока соблюдается принцип замещения Лискова. Например, в объектно-ориентированном языке C++ разрешено переопределять метод, возвращающий Animal, методом, возвращающим Tiger. Эта функция называется ковариантностью возвращаемого типа, и она довольно распространена в языках ООП. Однако это не особенность C #. - person Eric Lippert; 15.06.2011
comment
@ Эрик Липперт Вы абсолютно правы. Я знаком с ковариантностью типа возвращаемого значения и контравариантностью типа параметра. C# поддерживает его для типов делегатов с версии 1.0. Позже в C# 4.0 была добавлена ​​поддержка универсальных интерфейсов и типов делегатов. Спасибо, что указали на мою ошибку, я исправил ответ. - person Enrico Campidoglio; 15.06.2011

За исключением сокрытия исходного свойства:

public new DerivedRequest Request { get;set;}

Однако я настоятельно не рекомендую этого делать. Сокрытие чего-то, что должно быть переопределено, вызывает проблемы, особенно если свойство не является простым автоматически сгенерированным. Кроме того, если он используется в качестве интерфейса или базового класса, исходная реализация (в этом случае на один класс выше в дереве наследования). Если вы реализуете абстрактный класс или интерфейс, вы даже не сможете скрыть исходную сигнатуру, поскольку вы обязаны ее реализовать.

Обычно, если вы думаете об использовании ключевого слова new, вы на неверном пути. Есть случаи, когда это необходимо и требуется, однако в большинстве случаев это не так.

Вместо этого создайте другое свойство:

public DerivedRequest DerivedRequest {/* make adequate conversions here*/ }

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

person Femaref    schedule 14.06.2011
comment
Ну, как бы вы справились с сеттером? - person Vlad; 15.06.2011
comment
Любой DerivedRequest по-прежнему является Request ниже и может быть назначен напрямую. - person Femaref; 15.06.2011

Изменить: вы не можете изменить тип производного типа, но new может помочь:

В производном типе...

public new DerivedRequest request
{
   get{return (DerivedRequest) base.request;}
   set{base.request = value;}
}
public override Request request
{
   get{return base.request;}
   set{base.request = (DerivedRequest) value;} // Throws InvalidCastException if misused.
}
person agent-j    schedule 14.06.2011
comment
обычно это не называется переопределением - person Vlad; 15.06.2011

Это невозможно теоретически. Переопределение должно быть ковариантным для типа возвращаемого значения (то есть тип возвращаемого значения должен быть более конкретным или таким же) и контравариантным для параметра (то есть тип параметра должен быть менее конкретным или таким же). Таким образом, ваш новый тип должен быть эффективно одновременно ковариантным и контравариантным по отношению к Request — это означает, что единственный возможный тип — это просто Request.

По этой причине в C# не разрешено изменять тип свойств для переопределений.

person Vlad    schedule 14.06.2011
comment
Разве DerivedRequest не будет более конкретным, чем Request, и, следовательно, не сделает переопределение ковариантным для возвращаемого типа? Или я неправильно прочитал ваш комментарий? - person Trevor; 15.06.2011
comment
@threed: свойство состоит из геттера и сеттера. проблема с сеттером :-) - person Vlad; 15.06.2011