Поддерживает ли C # ковариантность возвращаемого типа?

88

Я работаю с .NET framework, и я действительно хочу иметь возможность создавать страницы нестандартного типа, которые использует весь мой веб-сайт. Проблема возникает, когда я пытаюсь получить доступ к странице из элемента управления. Я хочу иметь возможность возвращать мой конкретный тип страницы вместо страницы по умолчанию. Есть какой-либо способ сделать это?

public class MyPage : Page
{
    // My own logic
}

public class MyControl : Control
{
    public MyPage Page { get; set; }
}
Кайл Слеттен
источник

Ответы:

175

ОБНОВЛЕНИЕ: этот ответ был написан в 2011 году. После двух десятилетий того, как люди предлагали ковариантность возвращаемого типа для C #, похоже, что он, наконец, будет реализован; Я очень удивлен. См. Объявление внизу https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/ ; Я уверен, что подробности будут позже.


Похоже, вам нужна ковариация возвращаемого типа. C # не поддерживает ковариацию возвращаемого типа.

Ковариация возвращаемого типа - это когда вы переопределяете метод базового класса, который возвращает менее конкретный тип, на метод, который возвращает более конкретный тип:

abstract class Enclosure
{
    public abstract Animal Contents();
}
class Aquarium : Enclosure
{
    public override Fish Contents() { ... }
}

Это безопасно, потому что потребители Контента через вольер ожидают животного, и Аквариум обещает не только выполнить это требование, но, более того, дать более строгое обещание: животное всегда будет рыбой.

Этот вид ковариации не поддерживается в C # и вряд ли когда-либо будет поддерживаться. Он не поддерживается средой CLR. (Он поддерживается C ++ и реализацией C ++ / CLI в CLR; он делает это путем создания волшебных вспомогательных методов того типа, который я предлагаю ниже.)

(Некоторые языки также поддерживают контравариантность формальных параметров - вы можете переопределить метод, принимающий Fish, методом, принимающим Animal. И снова контракт выполняется; базовый класс требует, чтобы любая Fish обрабатывалась, а производный обещает обрабатывать не только рыбу, но и любое животное. Точно так же C # и CLR не поддерживают контравариантность формальных типов параметров.)

Чтобы обойти это ограничение, выполните следующие действия:

abstract class Enclosure
{
    protected abstract Animal GetContents();
    public Animal Contents() { return this.GetContents(); }
}
class Aquarium : Enclosure
{
    protected override Animal GetContents() { return this.Contents(); }
    public new Fish Contents() { ... }
}

Теперь вы получаете как преимущества переопределения виртуального метода, так и усиление набора текста при использовании чего-то типа Aquarium во время компиляции.

Эрик Липперт
источник
3
Отлично! Я забыл про прятаться!
Кайл Слеттен
3
хороший ответ как всегда. Некоторое время нам не хватало этих ответов.
Dhananjay
4
Есть ли основания для отказа от поддержки обратной ковариации и парам-контравариантности, доступных для чтения где угодно?
porges
26
@EricLippert Поскольку я фанат C #, мне всегда любопытно, что такое "закулисное", поэтому я просто должен спросить: учитывалась ли когда-либо ковариация возвращаемого типа для C # и считалась ли она слишком низкой ROI? Я понимаю ограничения времени и ошибок, но из вашего заявления «вряд ли когда-либо будет поддерживаться» похоже, что команда разработчиков на самом деле предпочла бы НЕ иметь этого на языке. С другой стороны, Java представила эту языковую функцию в Java 5, поэтому мне интересно, в чем разница в более широких, всеобъемлющих принципах, которые управляют процессом проектирования языка.
Cristian Diaconescu
7
@Cyral: Верно; он находится в списке ведра "умеренного интереса" - и находится там уже давно! Это могло случиться, но я настроен довольно скептически.
Эрик Липперт
8

С интерфейсами я обошел это, явно реализовав интерфейс:

public interface IFoo {
  IBar Bar { get; }
}
public class Foo : IFoo {
  Bar Bar { get; set; }
  IBar IFoo.Bar => Bar;
}
ChetPrickles
источник
2

Поместите это в объект MyControl:

 public new MyPage Page {get return (MyPage)Page; set;}'

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

В этом примере ковариация не нужна, поскольку она относительно проста. Все, что вы делаете, это наследуете базовый объект Pageот MyPage. Все, Controlчто вы хотите вернуть MyPageвместо того, Pageчтобы переопределить PageсвойствоControl

Жайс
источник
1

Да, он поддерживает ковариацию, но это зависит от того, чего именно вы пытаетесь достичь.

Я также часто использую дженерики для вещей, а это значит, что когда вы делаете что-то вроде:

class X<T> {
    T doSomething() {
    }

}

class Y : X<Y> {
    Y doSomethingElse() {
    }
}

var Y y = new Y();
y = y.doSomething().doSomethingElse();

И не «теряйте» свои типы.

Кейд Ру
источник
1

Это функция для предстоящего C # 9.0 (.Net 5), предварительную версию которого вы можете скачать сейчас.

Следующий код в настоящее время строит успешно (без предоставления: error CS0508: 'Tiger.GetFood()': return type must be 'Food' to match overridden member 'Animal.GetFood()')

class Food { }
class Meat : Food { }

abstract class Animal {
    public abstract Food GetFood();
}

class Tiger : Animal {
    public override Meat GetFood() => default;
}

class Program {
    static void Main() => new Tiger();
}
Нейрон
источник
0

Я не пробовал, но разве это не работает?

YourPageType myPage = (YourPageType)yourControl.Page;
AGoodDisplayName
источник
1
Да, это так. Я просто хочу постараться избежать повсюду.
Кайл Слеттен
0

Да. Есть несколько способов сделать это, и это только один из них:

Вы можете заставить свою страницу реализовывать некоторый настраиваемый интерфейс, который предоставляет метод под названием «GetContext» или что-то в этом роде, и он возвращает вашу конкретную информацию. Затем ваш элемент управления может просто запросить страницу и выполнить приведение:

var myContextPage = this.Page as IMyContextGetter;

if(myContextPage != null)
   var myContext = myContextPage.GetContext();

Затем вы можете использовать этот контекст, как хотите.

Tejs
источник
0

Вы можете получить доступ к своей странице из любого элемента управления, поднявшись по родительскому дереву. То есть

myParent = this;

while(myParent.parent != null)
  myParent = myParent.parent;

* Не компилировал и не тестировал.

Или получите родительскую страницу в текущем контексте (зависит от вашей версии).


Тогда мне нравится вот что: я создаю интерфейс с функциями, которые хочу использовать в элементе управления (например, IHostingPage).

Затем я приводил родительскую страницу «IHostingPage host = (IHostingPage) Parent;» и я готов вызвать функцию на нужной мне странице под своим контролем.

Хоган
источник
0

Я сделаю это так:

class R {
    public int A { get; set; }
}

class R1: R {
    public int B { get; set; }
}

class A
{        
    public R X { get; set; }
}

class B : A 
{
    private R1 _x;
    public new R1 X { get => _x; set { ((A)this).X = value; _x = value; } }
}
Неукротимый
источник