【问题标题】:How to restrict overrided property to be read only如何将覆盖的属性限制为只读
【发布时间】:2011-08-22 09:34:04
【问题描述】:

我有以下代码。

   class A
    {
        public virtual int BoardSize { get; set; }
    }

    class B : A
    {
        public override int BoardSize
        {
            get{return 100;}
        }
    }

    Class Client
    {
        B b = new B();
        b.BoardSize = 55;
    }

在基类中,属性是读/写的。但在派生类中它是只读的,应该总是返回 100。虽然当我运行代码时,我发现它总是按预期返回 100。

但是,我不希望客户在 B 类中设置 BoardSize。所以它根本不允许客户端写 b.BoardSize = 55

这怎么可能?

【问题讨论】:

  • 如果派生类不能遵守基类的约定,那么你就是在滥用继承。
  • @Martinho,我不同意你的派生类不遵守合同。合同正在完全履行。 OP 唯一想要的就是控制实现和访问级别。
  • @code4life,是的,你是绝对正确的。由于派生类可以修改实现,这不会破坏继承规则或滥用继承。
  • @code4life:据我所知,合同包括一个 BoardSize 的设置器。如果派生类没有,我称之为违约。
  • @anil:当然,派生类可以修改实现。但据我了解,您不想修改实现,而是要修改接口(删除setter)。

标签: c# properties derived-class base-class


【解决方案1】:

简短回答:嗯……你不能!
长答案:
你可以使用new从基类中隐藏BoardSize的实现:

class B : A
{
    public new int BoardSize
    {
        get{return 100;}
    }
}

这将使以下代码无效:

B b = new B();
b.BoardSize = 55;

但这只会让事情变得更糟!
以下代码有效:

A a = new B();
a.BoardSize = 55;
Console.WriteLine(a.BoardSize);
Console.WriteLine(((B)a).BoardSize);

它会打印出来

55
100

结论:
您可以使用new 来实现您的目标,但这会在您的代码中引入严重的问题。
如果您无法更改AB 需要从A 派生然后使用此代码:

class B : A
{
    public override int BoardSize
    {
        get{return 100;}
        set{throw new NotSupportedException();}
    }
}

这与 .NET 框架是一致的。例如,ReadOnlyCollection 在您调用 Add 时抛出此异常(在将其转换为 ICollection<T> 之后)。

【讨论】:

  • 我在最后一个样本中删除了我认为多余的“新”,否则它会与第一个样本共享问题。希望这就是你的意图。
  • @Martinho:是的,我忘记将new 更改为override。感谢您发现它。
  • 我认为这里的最佳答案涵盖了可能出现的问题的最佳选择。
【解决方案2】:

你有两个选择。

1) 在setter中抛出异常:

class A
{
    public virtual int BoardSize { get; set; }
}

class B : A
{
  public override int BoardSize
  {
    get { return 100; }
    set {
      throw new NotSupportedException("Cannot set BoardSize on class B.");
    }
  }
}

如果您的代码有一个路径,它可能会尝试在 B 的实例上设置 BoardSize,那么您应该准备好处理该异常。

2) 只允许派生类设置BoardSize

class A
{
  public int BoardSize { get; protected set; }
}

class B : A
{
  public B()
  {
    BoardSize = 100;
  }
}

class C : A
{
  public void SetBoardSize(int boardSize)
  {
    BoardSize = boardSize;
  }
}

【讨论】:

  • 如果你要扔,NotSupportedException 更合适。 InvalidOperation 用于“当方法调用对于对象的当前状态无效时”。由于它根本不依赖于当前状态,我更喜欢 NotSupported。
  • @Marthinho Fernandes:我想你是对的。我一直在想,对A 的引用是B 的实例对于设置BoardSize 是无效的“状态”。
【解决方案3】:

您想要的是 B 中的 属性,它隐藏了 A 中的属性:

class B : A
{
    public new int BoardSize
    {
        get{return 100};
    }
}

【讨论】:

  • 如果您通过对A 的引用访问B 实例的BoardSize,您将获得0 而不是100。很可能不是创建虚拟属性时想要的。
【解决方案4】:

看看这是否有效:

class A
{
    public virtual int BoardSize { get; protected set; }
}

【讨论】:

  • 来自 msdn “受保护的成员可以从声明它的类中访问,也可以从从声明该成员的类派生的任何类中访问”,所以我怀疑这是否可行。跨度>
  • "但是,我不希望客户在 B 类中设置 BoardSize。所以它根本不允许客户写 b.BoardSize = 55。"我假设使用 virtual 关键字和列出的继承,他仍然希望从 B 类中设置属性,而不是从 B 的外部实例中设置属性。保护 set 访问器可以做到这一点。
  • @Andy:它确实使代码示例不可编译,所以我想这可能有效。
  • 那不行。您应该使用 new 关键字并从那里控制访问。
  • @Andy - 这是一个公平的观点,我想知道@anil 在这里试图完成什么,以及是否有更好的方法。当然,正如其他人所指出的,设置基类的属性,然后使用 new 关键字读取派生类,您会得到一些奇怪的结果。
【解决方案5】:

如果派生类可能需要更改成员的外向方面(例如,在读/写和只读之间更改属性,或者派生类型中的方法返回更多派生类型而不是基类型中的同名方法),则成员本身应该是非虚拟的,但它的实现应该简单地包装对受保护的虚拟实现的调用。派生类型可能会影响调用受保护虚拟实现的基类成员,但不应在其中放置有意义的逻辑。

因此:

class A
{
    public int BoardSize { get {return GetBoardSize(); set {SetBoardSize(value); }
    protected abstract int GetBoardSize();
    protected abstract void SetBoardSize(int newSize);
    public abstract bool IsResizable { get; }
}

class B : A
{
    public new int BoardSize { get {return GetBoardSize(); }
    protected override int GetBoardSize() { return 100; }
    protected override void SetBoardSize(int newSize)
        { throw new NotSupportedException("Board is not resizable."); }
    public override bool IsResizable { get {return false;} }
}

尝试在B 类型的变量上设置BoardSize 将在编译时失败,因为该属性是只读的。将B 的引用存储在A 类型的变量中并尝试在其上设置BoardSize 将编译,但会在运行时引发异常。应记录该类以指定要设置 BoardSize 的代码应准备好考虑板可能无法调整大小的事实,并且如果调用者代码在这种情况下能够做一些明智的事情,它应该检查IsResizable 在尝试设置 BoardSize 之前。

【讨论】:

    猜你喜欢
    • 2010-12-02
    • 1970-01-01
    • 2013-07-24
    • 1970-01-01
    • 2011-07-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多