c#支持返回类型协方差吗?
本文关键字:方差吗 返回类型 支持 | 更新日期: 2023-09-27 17:50:35
我正在使用。net框架,我真的希望能够使所有我的网站使用自定义类型的页面。当我试图从控件访问页面时,问题就出现了。我希望能够返回我的特定类型的页面,而不是默认的页面。有什么办法可以做到吗?
public class MyPage : Page
{
// My own logic
}
public class MyControl : Control
{
public MyPage Page { get; set; }
}
更新:这个答案写于2011年。二十年来,人们一直在为c#提出返回类型协方差的建议,现在已经实现了。参见https://devblogs.microsoft.com/dotnet/c-9-0-on-the-record/中的协变返回。
听起来你想要的是返回类型协方差。c#不支持返回类型的协方差
返回类型协方差是指您用返回更特定类型的基类方法重写返回不太特定类型的基类方法:
abstract class Enclosure
{
public abstract Animal Contents();
}
class Aquarium : Enclosure
{
public override Fish Contents() { ... }
}
这是安全的,因为通过Enclosure的内容的消费者期望一个动物,水族馆承诺不仅满足这一要求,而且做出更严格的承诺:动物永远是鱼。
这种协方差在c#中是不支持的,而且不太可能被支持。CLR不支持它。(它由c++和CLR上的c++/CLI实现支持;它通过生成我下面建议的那种神奇的助手方法来实现。)
(一些语言也支持形式参数类型的逆变性——你可以用一个带Animal的方法覆盖一个带Fish的方法。再说一次,合同履行了;基类要求处理任何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;
}
这是即将到来的c# 9.0的一个特性。Net 5),您现在可以下载预览版。
下面的代码现在成功构建(没有给出:error CS0508: 'Tiger.GetFood()': return type must be 'Food' to match overridden member 'Animal.GetFood()'
)
class Food { }
class Meat : Food { }
abstract class Animal {
public abstract Food GetFood();
}
class Tiger : Animal {
public override Meat GetFood() => default;
}
class Program {
static void Main() => new Tiger();
}
将其放置在MyControl对象中即可:
public new MyPage Page {get return (MyPage)Page; set;}'
你不能重写属性,因为它返回不同的类型…但是你可以重新定义它
在本例中不需要协方差,因为它相对简单。你所做的就是从MyPage
继承基础对象Page
。任何想要返回MyPage
而不是Page
的Control
都需要重新定义Control
的Page
属性
是的,它支持协方差,但这取决于您想要实现的确切内容。
我也倾向于大量使用泛型,这意味着当您执行以下操作时:
class X<T> {
T doSomething() {
}
}
class Y : X<Y> {
Y doSomethingElse() {
}
}
var Y y = new Y();
y = y.doSomething().doSomethingElse();
我还没有试过,但这不是工作吗?
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; } }
}