如何在C#的子类中重写方法并返回子类型?

32

我有一个子类,其中包含一个重载的方法,我知道该方法始终返回在基类中声明的返回类型的特定子类型。如果我按照这种方式编写代码,它将无法编译。由于这可能没有意义,因此让我举一个代码示例:

class BaseReturnType { }
class DerivedReturnType : BaseReturnType { }

abstract class BaseClass {
    public abstract BaseReturnType PolymorphicMethod();
}

class DerivedClass : BaseClass {
    // Compile Error: return type must be 'BaseReturnType' to match 
    // overridden member 'BaseClass.PolymorphicMethod()'
    public override DerivedReturnType PolymorphicMethod() { 
        return new DerivedReturnType(); 
    }
}

有没有办法在C#中实现这一点?如果没有,那么实现类似功能的最佳方式是什么?为什么不允许这样做?它似乎不会导致任何逻辑不一致,因为从重载方法返回的任何对象仍然是。也许我还没有考虑到某些事情。或者也许原因是技术或历史原因。


我已将编译器错误作为注释包含在内。 - recursive
7个回答

22
很遗憾,C#不支持方法重写中的协变返回类型(以及逆变参数类型)。如果您正在实现接口,则可以使用“弱”版本显式实现它,并提供具有更强约束的公共版本。对于父类的简单重写,恐怕您没有这种奢侈品:( Marc提出了一个合理的解决方案 - 尽管它非常丑陋,而且方法隐藏通常会影响可读性。请原谅,Marc;) 我认为这实际上是CLR的限制,而不仅仅是语言限制 - 但我可能错了。 (作为历史事实,Java(语言)在1.5之前也有相同的限制 - 但它同时获得了协变和泛型。)

1
不要误会 ;-p,然而需要注意的是,这正是许多.NET BCL所使用的模式 - 例如,DbConnection具有CreateCommand,它通过“protected abstract” CreateDbCommand进行了包装。 - Marc Gravell
1
请参考Eric Lippert的评论,了解为什么C#不支持此功能。https://dev59.com/wXM_5IYBdhLWcg3wlEFH#1320710 - Ben Lings

19
如果您不介意,您可以使类变成通用类:

您可以使类成为通用类,如果这并不影响您:

    class BaseReturnType { }
    class DerivedReturnType : BaseReturnType { }

    abstract class BaseClass<T> where T : BaseReturnType
    {
        public abstract T PolymorphicMethod();
    }

    class DerivedClass : BaseClass<DerivedReturnType>
    {
        // Error: return type must be 'BaseReturnType' to match 
        // overridden member 'BaseClass.PolymorphicMethod()'
        public override DerivedReturnType PolymorphicMethod()
        {
            return new DerivedReturnType();
        }
    }

13

如果你想要覆盖一个方法但无法使用overridenew在同一类型中同时命名,你可以通过引入一个额外的方法来实现:

abstract class BaseClass
{
    public BaseReturnType PolymorphicMethod()
    { return PolymorphicMethodCore();}

    protected abstract BaseReturnType PolymorphicMethodCore();
}

class DerivedClass : BaseClass
{
    protected override BaseReturnType PolymorphicMethodCore()
    { return PolymorphicMethod(); }

    public new DerivedReturnType PolymorphicMethod()
    { return new DerivedReturnType(); }
}

现在你在每个级别上都有一个具有正确类型的PolymorphicMethod方法。


2

泛型不一定是解决问题的方法。特别是,Derived 的 type 不被认为是 Base 的 type。

首先,在派生类中添加一个新方法,该方法将返回正确类型的值。其次,将重写方法标记为不可重写,并让它委托给您的新方法。

就这样,您已经解决了问题。子类将无法重新扩展类型,因为它们必须重写您的新方法。

如果代码不正确,请见谅;我习惯使用 VB.net。

abstract class C1 {
    public abstract IEnumerable<Byte> F1();
}
class C2 : C1 {
    public sealed override IEnumerable<Byte> F1() {
        Return F2();
    }
    public overridable IList<Byte> F2() {
        Return {1, 2, 3, 4};
    }
}

1
class BaseReturnType { }
class DerivedReturnType : BaseReturnType { }

abstract class BaseClass {
    public abstract BaseReturnType PolymorphicMethod();
}

class DerivedClass : BaseClass {
    // Error: return type must be 'BaseReturnType' to match 
    // overridden member 'BaseClass.PolymorphicMethod()'
    public override BaseReturnType PolymorphicMethod() { 
        return new DerivedReturnType(); 
    }
}

这应该可以工作


我认为OP希望该方法被声明为DerivedReturnType,而不仅仅返回一个。 - Marc Gravell
我知道这是可能的,但如果你想将其用作DerivedReturnType,则需要强制转换(new DerivedClass()).PolymorphicMethod()。那可能是我最终要做的事情,但如果可以避免强制转换,我不喜欢使用它。 - recursive
他可以将返回值转换为Derived类型并在需要的地方使用。 - anand
@anand:是的,但那很痛苦,而且需要在执行时进行检查,而我们应该能够解释它在编译时是有效的。 - Jon Skeet

1

将派生类中的方法签名更改为:

 public override BaseReturnType PolymorphicMethod() 
 {
    return new DerivedReturnType();     
 }

C#不支持变体返回类型。您可以查看此帖子以了解使用泛型实现此操作的方法...http://srtsolutions.com/blogs/billwagner/archive/2005/06/17/covaraint-return-types-in-c.aspx

以下是在您的模型中使用泛型的示例:

public class BaseReturnType
{
}
public class DerivedReturnType : BaseReturnType
{
}

public abstract class BaseClass<T> where T : BaseReturnType
{
    public abstract T PolymorphicMethod();

}

public class DerviedClass : BaseClass<DerivedReturnType>
{
    public override DerivedReturnType PolymorphicMethod()
    {
        throw new NotImplementedException();
    }
}

0

在我看来,你需要返回一个接口,而不是一个基类。


1
首先,这并不总是适用的;其次,即使适用也可能没有帮助 —— 你可能想指示你正在返回 ISpecificInterface 的实现,而基类声称将返回 IGeneralInterface。 - Jon Skeet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接