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

时间:2011-04-18 21:18:06

标签: c# covariance

我正在使用.NET框架,我真的希望能够创建一个我所有网站都使用的自定义类型的页面。当我尝试从控件访问页面时出现问题。我希望能够返回我的特定类型的页面而不是默认页面。有没有办法做到这一点?

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

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

10 个答案:

答案 0 :(得分:145)

听起来你想要的是返回类型协方差。 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时获得更强的输入。

答案 1 :(得分:2)

将其置于MyControl对象中将起作用:

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

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

在这个例子中你不需要协方差,因为它相对简单。您所做的只是从Page继承基础对象MyPage。您要返回Control而不是MyPage的任何Page都需要重新定义Page的{​​{1}}属性

答案 2 :(得分:2)

此博客文章系列详细讨论了返回类型协方差的问题,甚至讨论了如何使用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代码实现它。

答案 3 :(得分:2)

使用接口我通过显式实现接口来解决它:

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

答案 4 :(得分:1)

是的,它支持协方差,但这取决于你想要达到的确切目标。

我也倾向于对事物使用泛型,这意味着当你做类似的事情时

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

}

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

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

而不是“失去”你的类型。

答案 5 :(得分:1)

这是upcoming C# 9.0 (.Net 5)的一项功能,您现在可以download a preview version

以下代码现在可以成功构建(不提供: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();
}

答案 6 :(得分:0)

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

myParent = this;

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

*没有编译或测试。

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


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

然后我转换父页面'IHostingPage host =(IHostingPage)Parent;'我已准备好从我的控制中调用我需要的页面上的功能。

答案 7 :(得分:0)

我没有尝试过,但这不起作用吗?

YourPageType myPage = (YourPageType)yourControl.Page;

答案 8 :(得分:0)

是。有多种方法可以做到这一点,这只是一个选择:

您可以使您的页面实现一些自定义接口,该接口公开名为“GetContext”的方法或其他东西,并返回您的特定信息。然后你的控件可以简单地请求页面和演员:

var myContextPage = this.Page as IMyContextGetter;

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

然后您可以根据自己的意愿使用该上下文。

答案 9 :(得分: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; } }
}