【问题标题】:Override Property with different compatible Type使用不同的兼容类型覆盖属性
【发布时间】:2011-03-09 15:39:25
【问题描述】:

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

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>();
        //...
    }
}

我尝试使用泛型,但在使用该类时给我带来了问题,因为我想在列表中存储不同类型的基类。

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

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

【问题讨论】:

    标签: c# generics inheritance properties .net-2.0


    【解决方案1】:

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

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

    基本上,我在中间添加了一个通用类,用于存储您正确键入的属性。假设您永远不需要从迭代List&lt;Base&gt; 成员的代码中访问Prop,这将起作用。 (如果需要,您始终可以向 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();
        }
    }
    

    编辑:添加 GetProp() 方法来说明如何从基类直接访问属性。

    【讨论】:

    • +1:这是对手头问题的干净解决方案。将基类与泛型部分分开。
    【解决方案2】:

    您不能覆盖属性的类型。看看下面的代码:

    StrBase s = new StrBase();
    Base b = s;
    

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

    b.prop = 5;
    

    整数可以转换为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;
    

    这是因为 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;
          }
        }
      }
    }
    

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

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

    【讨论】:

    • 第一个解决方案的简单性很有吸引力。但是,如果“prop”被设置为不同的 Type 值会发生什么? 10 年后你会如何看待这个问题?阳光下的任何新鲜事物;)
    • @BernoulliIT,我可能会重新考虑整体设计,因为我真的不需要这个;)根据我的经验,当读取允许不同的数据结构时,往往会出现这样的代码值类型,例如 JSON。通常最好将属性键入为object,并意识到您有时正在处理最好不键入的数据结构。试图把它硬塞进类型系统不会让它变得更好,只会更复杂。
    • 你将如何使用协变/逆变方法解决 OP 的问题?
    【解决方案3】:

    这是可能的,因为 C# 9.0

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

    (见Microsoft docs

    【讨论】:

      【解决方案4】:
      public class First
      {
          private int someV;
      
          public virtual object SomeV { get => someV; set => someV = (int)value; }
      
          public First() { }
      }
      
      public class Two : First
      {
          private string someV;
      
          public override object SomeV { get => someV; set => someV = value.ToString(); }
      
          public Two() { }
      }
      

      以及这些的使用:

      First firstClass = new First();
      firstClass.SomeV = 1;
      
      Two twoClass = new Two();
      twoClass.SomeV = "abcd";
      

      【讨论】:

        猜你喜欢
        • 2018-07-26
        • 2018-08-31
        • 2019-09-29
        • 2019-06-12
        • 2013-04-05
        • 2021-12-06
        • 2019-01-12
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多