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

发布于 2024-11-02 07:16:52 字数 257 浏览 1 评论 0原文

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

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

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

I'm working with the .NET framework and I really want to be able to make a custom type of page that all of my website uses. The problem comes when I am trying to access the page from a control. I want to be able to return my specific type of page instead of the default page. Is there any way to do this?

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

征﹌骨岁月お 2024-11-09 07:16:52

更新:这个答案写于 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 的内容消费者期望动物,而 Aquarium 承诺不仅满足该要求,但此外,还要做出更严格的承诺:该动物始终是鱼。

C# 不支持这种协方差,而且不太可能永远支持。 CLR 不支持它。 (它受到 C++ 以及 CLR 上的 C++/CLI 实现的支持;它通过生成我在下面建议的那种神奇的辅助方法来实现这一点。)

(某些语言也支持形式参数类型逆变——您可以覆盖它一个接受 Fish 的方法和一个接受 Animal 的方法同样得到满足;基类要求处理任何 Fish,并且派生类承诺不仅处理鱼,还处理任何动物。 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 时获得更强的打字能力。


UPDATE: This answer was written in 2011. After two decades of people proposing return type covariance for C# they have been implemented. See Covariant Returns in https://devblogs.microsoft.com/dotnet/c-9-0-on-the-record/.


It sounds like what you want is return type covariance. C# does not support return type covariance.

Return type covariance is where you override a base class method that returns a less-specific type with one that returns a more specific type:

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

This is safe because consumers of Contents via Enclosure expect an Animal, and Aquarium promises to not only fulfill that requirement, but moreover, to make a more strict promise: that the animal is always a fish.

This kind of covariance is not supported in C#, and is unlikely to ever be supported. It is not supported by the CLR. (It is supported by C++, and by the C++/CLI implementation on the CLR; it does so by generating magical helper methods of the sort I suggest below.)

(Some languages support formal parameter type contravariance as well -- that you can override a method that takes a Fish with a method that takes an Animal. Again, the contract is fulfilled; the base class requires that any Fish be handled, and the derived class promises to not only handle fish, but any animal. Similarly, C# and the CLR do not support formal parameter type contravariance.)

The way you can work around this limitation is to do something like:

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() { ... }
}

Now you get both the benefits of overriding a virtual method, and getting stronger typing when using something of compile-time type Aquarium.

玩套路吗 2024-11-09 07:16:52

通过接口,我通过显式实现接口来解决这个问题:

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

With interfaces I got around it by explicitly implementing the interface:

public interface IFoo {
  IBar Bar { get; }
}
public class Foo : IFoo {
  Bar Bar { get; set; }
  IBar IFoo.Bar => Bar;
}
审判长 2024-11-09 07:16:52

这是即将推出的 C# 9.0 (.Net 5) 的一项功能您现在可以下载预览版本

以下代码现在可以成功构建(不会给出:错误 CS0508: 'Tiger.GetFood()': 返回类型必须是 'Food' 才能匹配重写成员 '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();
}

This is a feature for the upcoming C# 9.0 (.Net 5) of which you can download a preview version now.

The following code now builds successfully (without giving: 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();
}
篱下浅笙歌 2024-11-09 07:16:52

将其放置在 MyControl 对象中是可行的:

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

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

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

Placing this in the MyControl object would work:

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

You can't override the property because it returns a different type... but you can redefine it.

You don't need covariance in this example, since it is relatively simple. All you're doing is inheriting the base object Page from MyPage. Any Control that you want to return MyPage instead of Page needs to redefine the Page property of the Control

瑶笙 2024-11-09 07:16:52

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

我也倾向于大量使用泛型,这意味着当您执行以下操作时:

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

}

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

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

并且不会“丢失”您的类型。

Yes, it supports covariance, but it depends upon the exact thing you are trying to achieve.

I also tend to use generics a lot for things, which means that when you do something like:

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

}

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

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

And not "lose" your types.

梦幻之岛 2024-11-09 07:16:52

我没试过,但这不行吗?

YourPageType myPage = (YourPageType)yourControl.Page;

I haven't tried it, but doesn't this work?

YourPageType myPage = (YourPageType)yourControl.Page;
栖竹 2024-11-09 07:16:52

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

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

var myContextPage = this.Page as IMyContextGetter;

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

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

Yes. There are multiple ways of doing this, and this is just one option:

You can make your page implement some custom interface that exposes a method called "GetContext" or something, and it returns your specific information. Then your control can simply request the page and cast:

var myContextPage = this.Page as IMyContextGetter;

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

Then you can use that context however you wish.

残龙傲雪 2024-11-09 07:16:52

您可以通过遍历父树从任何控件访问您的页面。即

myParent = this;

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

* 没有编译或测试。

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


然后我喜欢做的是这样的:我创建一个界面,其中包含我想在控件中使用的功能(例如 IHostingPage)

然后我投射父页面 'IHostingPage host = (IHostingPage)Parent;'我已准备好从我的控制中调用我需要的页面上的函数。

You can access your page from any control by walking up the parent tree. That is

myParent = this;

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

*Did not compile or test.

Or get the parent page in the current context (depends on your version).


Then what I like to do is this: I create an interface with the functions I want to use in the control (for example IHostingPage)

Then I cast the parent page 'IHostingPage host = (IHostingPage)Parent;' and I am all set to call the function on the page I need from my control.

寻找一个思念的角度 2024-11-09 07:16:52

我会这样做:

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

I will do it in this way:

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; } }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文