Поддерживает ли 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;
}

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;

1
Да, это так. Я просто хочу постараться избежать повсюду.
Кайл Слеттен

0

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

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

var myContextPage = this.Page as IMyContextGetter;

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

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


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; } }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.