Поддерживает ли C# ковариацию возвращаемого типа?
Я работаю с .NET framework, и я действительно хочу иметь возможность создавать пользовательский тип страницы, который использует весь мой сайт. Проблема приходит, когда я пытаюсь получить доступ к странице из-под контроля. Я хочу иметь возможность вернуть мой конкретный тип страницы вместо страницы по умолчанию. Есть ли способ сделать это?
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; он делает это путем создания магических вспомогательных методов типа, который я предлагаю ниже.)
(некоторые языки также поддерживают контравариантность формального типа параметров - что вы можете переопределить метод, который принимает рыбу, с помощью метода, который принимает животное. Опять же, контракт базовый класс требует обработки любой рыбы, а производный класс обещает обрабатывать не только рыбу, но и любое животное. Аналогично, 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; }
да. Существует несколько способов сделать это, и это только один вариант:
вы можете заставить свою страницу реализовать некоторый пользовательский интерфейс, который предоставляет метод под названием "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;' и я все настроен для вызова функции на странице мне нужно из моего элемента управления.