【问题标题】:Does C# support return type covariance?C# 是否支持返回类型协方差?
【发布时间】:2011-04-18 21:18:05
【问题描述】:

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

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

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

【问题讨论】:

标签: c# covariance


【解决方案1】:

更新:这个答案写于 2011 年。经过 20 年人们为 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() { ... }
}

这是安全的,因为 Contents via Enclosure 的消费者期待动物,而 Aquarium 承诺不仅满足该要求,而且做出更严格的承诺:动物始终是鱼。

这种协方差在 C# 中不受支持,也不太可能得到支持。 CLR 不支持它。 (C++ 和 CLR 上的 C++/CLI 实现都支持它;它通过生成我在下面建议的那种神奇的辅助方法来实现。)

(一些语言也支持形式参数类型逆变——你可以用一个接受一个动物的方法覆盖一个接受一个鱼的方法。同样,合同被履行了;基类要求处理任何鱼,并且派生类承诺不仅可以处理鱼,还可以处理任何动物。同样,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 编译时类型的东西时获得更强的输入。

【讨论】:

  • 是否有理由不支持可在任何地方读取的返回协方差和参数逆变?
  • @Porges:我们不需要为不支持功能提供理由。功能很昂贵,并且默认情况下未实现。只有在考虑、设计、指定、实施、测试并交付给客户时,它们才会被实施。
  • @EricLippert 作为一个 C# 追随者,我总是对“后台”很好奇,所以我只想问:C# 是否考虑过返回类型协方差并认为其投资回报率太低?我理解时间和 bugdet 的限制,但从你的“不太可能得到支持”的声明中,听起来设计团队实际上宁愿不在语言中使用它。另一方面,Java 在 Java 5 中引入了这一语言特性,所以我想知道在支配语言设计过程的更大、总体原则方面有何不同。
  • @Cyral:正确;它在“中等兴趣”桶的列表中——这是它长期以来的位置!这可能会发生,但我很怀疑。
  • 遗憾的是,该功能没有进入 C# 7。但跟踪它仍然存在问题:github.com/dotnet/roslyn/issues/357
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

    将它放在 MyControl 对象中会起作用:

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

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

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

    【讨论】:

      【解决方案4】:

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

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

      class X<T> {
          T doSomething() {
          }
      
      }
      
      class Y : X<Y> {
          Y doSomethingElse() {
          }
      }
      
      var Y y = new Y();
      y = y.doSomething().doSomethingElse();
      

      不要“失去”你的类型。

      【讨论】:

        【解决方案5】:

        这是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】:

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

          YourPageType myPage = (YourPageType)yourControl.Page;
          

          【讨论】:

          • 是的,确实如此。我只是想尽量避免到处施放。
          【解决方案7】:

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

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

          var myContextPage = this.Page as IMyContextGetter;
          
          if(myContextPage != null)
             var myContext = myContextPage.GetContext();
          

          然后,您可以随意使用该上下文。

          【讨论】:

            【解决方案8】:

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

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

            *没有编译或测试。

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


            那我喜欢做的是:我用我想在控件中使用的功能创建一个界面(例如IHostingPage)

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

            【讨论】:

              【解决方案9】:

              我会这样:

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

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2012-03-03
                • 1970-01-01
                • 2017-05-15
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多