Поддерживает ли C# ковариацию типа возвращаемого значения?

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

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

public class MyControl : Control
{
    public MyPage Page { get; set; }
}

9 ответов

Решение

Похоже, что вы хотите, это ковариация типа возврата. 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 во время компиляции.

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

public interface IFoo {
  IBar Bar { get; }
}
public class Foo : IFoo {
  Bar Bar { get; set; }
  IBar IFoo.Bar => Bar;
}

Пока это невозможно, но, как было объявлено в сообщении Мэдса Торгерсена в марте 2020 года, это запланированная функция для грядущего C# 9.0.

abstract class Animal
{
    public abstract Food GetFood();
    ...
}
class Tiger : Animal
{
    public override Meat GetFood() => ...;
}

Размещение этого в объекте MyControl будет работать:

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

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

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

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

http://www.simple-talk.com/community/blogs/simonc/archive/2010/07/14/93495.aspx

http://www.simple-talk.com/community/blogs/simonc/archive/2010/07/16/93516.aspx

http://www.simple-talk.com/community/blogs/simonc/archive/2010/07/19/93562.aspx

Извините за простую публикацию ссылок, но это довольно подробно, и цитирование фрагментов здесь не будет таким полезным. Он показывает, как этого можно достичь с помощью кода IL.

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

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

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

}

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

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

И не "потерять" ваши типы.

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

myParent = this;

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

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

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


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

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

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

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

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

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

var myContextPage = this.Page as IMyContextGetter;

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

Тогда вы можете использовать этот контекст по своему желанию.

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

YourPageType myPage = (YourPageType)yourControl.Page;
Другие вопросы по тегам