Поддерживает ли C # ковариацию типа возвращаемого значения? - PullRequest
71 голосов
/ 19 апреля 2011

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

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

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

Ответы [ 9 ]

147 голосов
/ 19 апреля 2011

Звучит так, как будто вы хотите получить ковариацию типа возврата.C # не поддерживает ковариацию возвращаемого типа.

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

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

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

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

(Некоторые языки также поддерживают формальную контрастность типов параметров- что вы можете переопределить метод, который принимает Fish, методом, который принимает Animal. Опять же, контракт выполнен, базовый класс требует обработки любой рыбы, а производный класс обещает обрабатывать не только рыбу, но и любую другую.Аналогично, 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 во время компиляции.

4 голосов
/ 29 сентября 2017

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

public interface IFoo {
  IBar Bar { get; }
}
public class Foo : IFoo {
  Bar Bar { get; set; }
  IBar IFoo.Bar => Bar;
}
3 голосов
/ 26 апреля 2012

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

http://www.simple -talk.com / сообщества / блоги / simonc / Архив / 2010/07/14 / 93495.aspx

http://www.simple -talk.com / сообщества / блоги / simonc / Архив / 2010/07/16 / 93516.aspx

http://www.simple -talk.com / сообщества / блоги / simonc / Архив / 2010/07/19 / 93562.aspx

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

2 голосов
/ 19 апреля 2011

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

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

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

1 голос
/ 19 апреля 2011

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

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

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

}

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

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

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

0 голосов
/ 18 мая 2017

Я сделаю это следующим образом:

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; } }
}
0 голосов
/ 19 апреля 2011

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

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

var myContextPage = this.Page as IMyContextGetter;

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

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

0 голосов
/ 19 апреля 2011

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

YourPageType myPage = (YourPageType)yourControl.Page;
0 голосов
/ 19 апреля 2011

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

myParent = this;

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

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

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


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

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

...