C#是否支持返回类型协方差?


84

我正在使用.NET框架,但我确实希望能够制作一种供我所有网站使用的自定义类型的页面。当我尝试从控件访问页面时,问题就来了。我希望能够返回我的特定页面类型而不是默认页面。有什么办法吗?

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

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

Answers:


169

更新:这个答案写于2011年。经过二十多年的人们提出C#的返回类型协方差后,看起来终于可以实现了。我很惊讶。有关公告,请参见https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/的底部;我相信细节会跟着。


听起来您想要的是返回类型协方差。C#不支持返回类型协方差。

在返回类型协方差中,您可以覆盖一个基类方法,该方法返回一个特定性较低的类型,而一个返回特定性较高的类型:

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

这是安全的,因为通过附件访问内容的消费者希望有动物,而水族馆承诺不仅要满足这一要求,而且要做出更严格的承诺:动物永远是鱼。

这种协方差在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时获得了更强大的键入功能。


3
完善!我忘了躲藏!
凯尔·斯莱滕

3
一如既往的好答案。我们错过了这些答案已有一段时间了。
Dhananjay

4
是否有理由不支持可在任何地方读取的返回协方差和参数协方差?
2012年

26
@EricLippert作为C#分组用户,我一直对“后台”感到好奇,所以我只想问:C#是否考虑过返回类型协方差并且认为ROI太低?我了解时间和bugtet的限制,但是从您的“不可能得到支持”的说法看来,设计团队实际上宁愿不使用此语言。另一方面,Java在Java 5中引入了该语言功能,所以我想知道控制语言设计过程的更大,总体原则之间有什么区别。
Cristian Diaconescu 2012年

7
@Cyral:正确;它已列在“中等兴趣”类别中-很长时间了!可能会发生,但是我非常怀疑。
埃里克·利珀特

8

对于接口,我通过显式实现接口来解决:

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

2

将其放置在MyControl对象中将起作用:

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

您无法覆盖属性,因为它返回了不同的类型...但是您可以重新定义它。

在此示例中,您不需要协方差,因为它相对简单。您要做的就是Page从中继承基础对象MyPage。任何Control你想返回MyPage,而不是Page需要重新定义Page的属性Control


1

是的,它支持协方差,但这取决于您要实现的确切目标。

我也倾向于将泛型用于很多事情,这意味着当您执行以下操作时:

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

}

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

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

而不是“丢掉”您的类型。


1

这是即将发布的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();
}

0

我还没有尝试过,但这行不通吗?

YourPageType myPage = (YourPageType)yourControl.Page;

1
是的,确实如此。我只想尝试避免在整个地方投射。
凯尔·斯莱滕

0

是。有多种方法可以执行此操作,而这只是一种选择:

您可以使页面实现一些自定义接口,该接口公开一个称为“ GetContext”的方法或类似的方法,并返回您的特定信息。然后,您的控件可以简单地请求页面并进行转换:

var myContextPage = this.Page as IMyContextGetter;

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

然后,您可以根据需要使用该上下文。


0

您可以通过上级父树从任何控件访问页面。那是

myParent = this;

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

*未编译或测试。

或在当前上下文中获取父页面(取决于您的版本)。


然后我想做的是:我创建一个要在控件中使用的函数的接口(例如IHostingPage)

然后,我将父页面强制转换为“ IHostingPage host =(IHostingPage)Parent;”。并且我已经准备好从控件中调用所需页面上的函数。


0

我将以这种方式进行:

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; } }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.