在C#中使用派生返回类型覆盖抽象属性

34

我有四个类。Request、DerivedRequest、Handler和DerivedHandler。Handler类有一个属性,声明如下:

public abstract Request request { get; set; }

DerivedHandler需要重写此属性,以便返回DerivedRequest:

public override DerivedRequest request { get; set; }

有人对如何使这个工作有什么想法吗?


这并不是严格的面向对象编程,因为它违反了接口。某些类型的setter操作(具有非派生值)将会意外抛出异常。 - recursive
在这种情况下,我想我不需要一个setter。我可以创建一个私有属性并在构造函数中设置它。这将解决setter操作异常问题,对吗? - Trevor
在这种情况下,您不需要覆盖该属性。只需让构造函数仅接受DerivedRequest即可。 - recursive
6个回答

21

这并不是一个好的结构方式。可以采取以下方法之一:

1)只需不改变返回类型,在子类中正常重写即可。在DerivedHandler中,您可以使用Request的基类签名返回DerivedRequest的实例。任何使用此代码的客户端都可以选择将其强制转换为DerivedRequest

2)如果它们不应该是多态的,请改用泛型。

public abstract class HandlerBase<T> where T: Request
{
    public abstract T Request {get;set;}
}

public class Handler: HandlerBase<Request>()

public class DerivedHandler: HandlerBase<DerivedRequest>()

如果有多个情况,例如我想以这样的方式覆盖几个属性,使它们使用派生类,那么我需要做类似于 class HandlerBase<A,B,C,..> where A:classA, B:classB,... 的事情吗? - Javidan

6
在C#语言中,除非你用另一个具有相同名称的方法替换它,否则不允许更改继承方法的签名。这种技术被称为“成员隐藏”或“影藏”。
如果你使用的是.NET 2.0或更高版本,你可以通过将Request属性的返回类型转换为Handler类的泛型类型参数来解决这个问题。然后,DerivedHandler类会指定DerivedRequest类作为该类型参数的参数。
以下是一个示例:
// Handler.cs
public class Handler<TRequest> where TRequest : Request
{
    public TRequest Request { get; set; }
}

// DerivedHandler.cs
public class DerivedHandler : Handler<DerivedRequest>
{
}

3
确实可以这样做,“OOP”中没有任何禁止您这样做的内容,只要保持了里氏替换原则。例如,在面向对象语言“C ++”中,可以合法地重写返回动物的方法以返回老虎的方法。这个特性称为“返回类型协变”,在面向对象编程语言中很常见。但这不是C#的一个特性。 - Eric Lippert
@Eric Lippert 你说得完全正确。我熟悉返回类型协变和参数类型逆变。自从 C# 1.0 版本以来,它已经支持委托类型的这种情况。后来,在 C# 4.0 中也添加了对泛型接口和委托类型的支持。感谢指出我的错误,我已更正了答案。 - Enrico Campidoglio

5

除了隐藏原始属性外:

public new DerivedRequest Request { get;set;}

然而,我强烈反对这样做。隐藏本应被覆盖的内容会引发麻烦,特别是如果该属性不是一个简单的自动生成属性。此外,如果将其用作接口或基类,则原始实现(在这种情况下,在继承树中更高一级的类)也会受到影响。如果您正在实现抽象类或接口,则甚至无法隐藏原始签名,因为您必须实现它。
通常,如果您考虑使用“new”关键字,则走错了路。有些情况下是必要且必需的,但在大多数情况下并非如此。
相反,请创建另一个属性:
public DerivedRequest DerivedRequest {/* make adequate conversions here*/ }

那样一来,您就可以清楚地了解面向对象编程方面的信息,并以清晰易懂的方式获取它们。

那么,你会如何处理setter? - Vlad
1
任何“DerivedRequest”在更深层次上仍然是一个“Request”,可以直接赋值。 - Femaref

1

编辑:

您无法更改派生类型的类型,但是new可能会有所帮助:

在派生类型中...

public new DerivedRequest request
{
   get{return (DerivedRequest) base.request;}
   set{base.request = value;}
}
public override Request request
{
   get{return base.request;}
   set{base.request = (DerivedRequest) value;} // Throws InvalidCastException if misused.
}

通常不会称之为“override”。 - Vlad

0
public class Request{}

public class DerivedRequest : Request{}

public class Handler<T>
  where T : Request
{
  public abstract T Request { get; set; }
}

public class DerivedHandler : Handler<DerivedRequest>
{
  public override DerivedRequest Request { get; set; }
}

0

这在理论上是不可能的。覆盖必须对返回类型协变(即,返回类型必须更具体或相同),并且对于参数来说是逆变的(即,参数类型必须更少具体或相同)。因此,您的新类型必须在Request方面同时具有协变和逆变性--这意味着,唯一可能的类型就是Request

出于这个原因,在C#中不允许更改覆盖属性的类型。


派生请求(DerivedRequest)不是比请求(Request)更具体吗? 因此,重写可以使返回类型协变。还是我误解了你的评论? - Trevor
@threed:一个属性由getter和setter组成。问题出在setter上 :-) - Vlad

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