43

据我所知,在 C# 2.0 中无法执行以下操作

public class Father
{
    public virtual Father SomePropertyName
    {
        get
        {
            return this;
        }
    }
}

public class Child : Father
{
    public override Child SomePropertyName
    {
        get
        {
            return this;
        }
    }
}

我通过在派生类中将属性创建为“新”来解决该问题,但这当然不是多态的。

public new Child SomePropertyName

2.0有解决方案吗?3.5 中解决此问题的任何功能如何?

4

9 回答 9

44

可以重新声明(新建),但不能同时重新声明和覆盖(同名)。一种选择是使用受保护的方法来隐藏细节 - 这允许同时进行多态性和隐藏:

public class Father
{
    public Father SomePropertyName
    {
        get {
            return SomePropertyImpl();
        }
    }
    protected virtual Father SomePropertyImpl()
    {
        // base-class version
    }
}

public class Child : Father
{
    public new Child SomePropertyName
    {
        get
        { // since we know our local SomePropertyImpl actually returns a Child
            return (Child)SomePropertyImpl();
        }
    }
    protected override Father SomePropertyImpl()
    {
        // do something different, might return a Child
        // but typed as Father for the return
    }
}
于 2008-10-01T11:08:06.760 回答
29

由于类型安全问题,这在任何 .NET 语言中都是不可能的。在类型安全的语言中,您必须为返回值提供协变,为参数提供逆变。拿这个代码:

class B {
    S Get();
    Set(S);
}
class D : B {
    T Get();
    Set(T);
}

对于这些Get方法,协方差意味着它T必须是S或派生自S. 否则,如果您对D存储在变量 typed 中的类型对象的引用B,当您调用时,B.Get()您将不会获得可表示为Sback 的对象——这会破坏类型系统。

对于这些Set方法,逆变意味着T必须是SS派生自的类型。否则,如果您对D存储在变量 typed 中的类型对象的引用B,当您调用B.Set(X)where Xis of typeS但不是 typeT时,D::Set(T)将得到一个它不期望的类型的对象。

在 C# 中,有一个有意识的决定,即在重载属性时不允许更改类型,即使它们只有一个 getter/setter 对,因为否则它会产生非常不一致的行为(“你的意思是,我可以在一个带有 getter,但不是一个同时带有 getter 和 setter 的?为什么不呢?!?“ - 匿名交替宇宙新手)。

于 2008-10-01T11:59:46.323 回答
13

不,但您可以在 2 及更高版本中使用泛型:

public class MyClass<T> where T: Person
{
    public virtual T SomePropertyName
    {
        get
        {
            return  ...;
        }
    }
}

那么父亲和孩子是同一类的通用版本

于 2008-10-01T11:09:21.577 回答
7

来自维基百科

在 C# 编程语言中,该语言的 2.0 版中添加了对委托的返回类型协变和参数逆变的支持。方法覆盖既不支持协变也不支持逆变。

但是,它没有明确说明属性的协方差。

于 2008-10-01T11:08:21.500 回答
5

现代答案

C# 9开始,支持返回类型协变。这是从该链接复制的基本示例:

class Compilation ...
{
    public virtual Compilation WithOptions(Options options)...
}

class CSharpCompilation : Compilation
{
    public override CSharpCompilation WithOptions(Options options)...
}
于 2021-08-02T19:36:08.990 回答
2

您可以为父亲和孩子创建一个公共接口并返回该接口的类型。

于 2008-10-01T11:18:15.377 回答
2

这是我能来的最接近的(到目前为止):

public sealed class JustFather : Father<JustFather> {}

public class Father<T> where T : Father<T>
{ 
    public virtual T SomePropertyName
    { get { return (T) this; }
    }
}

public class Child : Father<Child>
{ 
    public override Child SomePropertyName
    { get { return  this; }
    }
}

如果没有JustFather该类,您将无法实例化 a Father<T>,除非它是其他派生类型。

于 2008-10-09T23:55:35.603 回答
1

不,C# 不支持这个想法(它被称为“返回类型协方差”)。但是,您可以这样做:

public class FatherProp
{
}

public class ChildProp: FatherProp
{
}


public class Father
{
    public virtual FatherProp SomePropertyName
    {
        get
        {
            return new FatherProp();
        }
    }
}


public class Child : Father
{
    public override FatherProp SomePropertyName
    {
        get
        {
            // override to return a derived type instead
            return new ChildProp();
        }
    }
}

即使用基类定义的契约,但返回派生类型。我制作了一个更详细的示例以使这一点更清楚 - 再次返回“this”不会改变任何事情。

可以(但很麻烦)测试返回的对象的实际类型(即“如果 someObject 是 ChildProp”),但最好在其上调用一个对其类型执行正确操作的虚拟方法。

基类虚方法(在本例中为虚拟属性)不仅有一个实现,而且还定义了一个约定:如果子类满足这个约定,它可以提供 SomePropertyName 的不同实现(即 SomePropertyName 返回一个类型为“父亲道具”)。返回从“FatherProp”派生的“ChildProp”类型的对象符合本合同。但是您不能更改“子”中的合同 - 该合同适用于“父亲”的所有后代。

如果您退后一步,看看您的更广泛的设计,您可能还想考虑 C# 工具包中的其他语言结构 - 泛型或接口。

于 2008-10-01T11:06:28.167 回答
1

不,C# 不支持这个想法(它被称为“返回类型协方差”)。

来自维基百科:

在 C# 编程语言中,该语言的 2.0 版中添加了对委托的返回类型协变和参数逆变的支持。方法覆盖既不支持协变也不支持逆变。

可以重新声明(新建),但不能同时重新声明和覆盖(同名)。一种选择是使用受保护的方法来隐藏细节 - 这允许同时进行多态性和隐藏:

最好的解决方案是使用泛型:

public class MyClass<T> where T: Person
{
   public virtual T SomePropertyNameA
   {        
      get { return  ...; }    
   }
}//Then the Father and Child are generic versions of the same class
于 2008-10-01T11:56:11.853 回答