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


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

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

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

9 ответов:

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

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

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

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

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

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

размещение этого в объекте 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();

и не "потерять" свои типы.

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

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

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

YourPageType myPage = (YourPageType)yourControl.Page;

да. Существует несколько способов сделать это, и это только один вариант:

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

var myContextPage = this.Page as IMyContextGetter;

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

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

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

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