使用不同的兼容类型覆盖属性

Rai*_*erM 18 c# generics inheritance properties .net-2.0

我需要一个带有属性的基类,在这里我可以派生具有相同属性但不同(兼容)类型的类.基类可以是抽象的.

public class Base
{
    public virtual object prop { get; set; }
}

public class StrBase : Base
{
    public override string prop { get; set; } // compiler error
}

public class UseIt
{
    public void use()
    {
        List<Base> l = new List<Base>();
        //...
    }
}
Run Code Online (Sandbox Code Playgroud)

我尝试使用Generics但是在使用该类时会给我一个问题,因为我想在List中存储不同类型的基类.

public class BaseG<T>
{
    public T prop { get; set; }
}

public class UseIt
{
    public void use()
    {
        List<BaseG> l = new List<BaseG>(); // requires type argument
        //...
    }
}
Run Code Online (Sandbox Code Playgroud)

And*_*son 26

这是建议解决方案的另一种方法:

public abstract class Base
{
    public abstract void Use();
    public abstract object GetProp();
}

public abstract class GenericBase<T> : Base
{
    public T Prop { get; set; }

    public override object GetProp()
    {
        return Prop;
    }
}

public class StrBase : GenericBase<string>
{
    public override void Use()
    {
        Console.WriteLine("Using string: {0}", Prop);
    }
}

public class IntBase : GenericBase<int>
{
    public override void Use()
    {
        Console.WriteLine("Using int: {0}", Prop);
    }
}
Run Code Online (Sandbox Code Playgroud)

基本上我在中间添加了一个通用类,用于存储正确类型的属性.假设您永远不需要Prop从迭代成员的代码访问,这将工作List<Base>.(你总是可以添加一个抽象的方法Base称为GetProp如果这是要求蒙上通用的对象.)

样品用法:

class Program
{
    static void Main(string[] args)
    {
        List<Base> l = new List<Base>();

        l.Add(new StrBase {Prop = "foo"});
        l.Add(new IntBase {Prop = 42});

        Console.WriteLine("Using each item");
        foreach (var o in l)
        {
            o.Use();
        }
        Console.WriteLine("Done");
        Console.ReadKey();
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:添加了GetProp()方法,以说明如何从基类直接访问该属性.


Nie*_*est 7

您无法覆盖属性的类型.看看下面的代码:

StrBase s = new StrBase();
Base b = s;
Run Code Online (Sandbox Code Playgroud)

这是完全有效的代码.但是当你尝试这样做时会发生什么?

b.prop = 5;
Run Code Online (Sandbox Code Playgroud)

整数可以转换为object,因为一切都是从中派生的object.但由于b实际上是一个StrBase实例,它必须以某种方式将整数转换为字符串,但它不能.这就是为什么不允许您覆盖该类型的原因.

同样的原则适用于泛型:

List<BaseG<object>> l = new List<BaseG<object>>();
BaseG<string> s = new BaseG<string>();

// The compiler will not allow this.
l.add(s);

// Here's the same problem, convert integer to string?
BaseG<object> o = l[0];
o.prop = 5;
Run Code Online (Sandbox Code Playgroud)

这是因为C#2.0中的泛型类型是不变的.C#4.0确实允许这种类型的转换,称为协方差和逆变.

解决方案

一个选项是object在需要时将背面强制转换为字符串.您可以在子类中添加类型验证:

public class StrBase : Base
{
  private string propValue;

  public override object prop {
    get
    {
      return this.propValue;
    }
    set
    {
      if (value is string)
      {
        this.propValue = (string)value;
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

您还可以在子类中公开类型安全的属性:

public class StrBase : Base
{
  public string strProp {
    get
    {
      return (string)this.prop;
    }
    set
    {
      this.prop = value;
    }
  }
}
Run Code Online (Sandbox Code Playgroud)


mar*_*sze 5

从C# 9.0开始这是可能的

从 C# 9.0 开始,重写方法支持协变返回类型。

(参见微软文档

  • 这是 co/contra-variance 解释的[链接](https://learn.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance) (3认同)
  • 对于属性,这只适用于**只读**属性。 (2认同)