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