[英]Why is there no compiler error when setting an overridden property that is read-only?
我一直在学习 C# 继承和接口( 1 、 2 、 3 )。 在下面的示例中,为什么在派生类中设置属性并通过 auto 属性使属性只读时不会出现编译器错误?
using System;
public interface IFoo {
string Title { get; set; }
}
public abstract class AbstractFoo : IFoo {
public virtual string Title { get; set; }
}
public class Foo : AbstractFoo {
public string Title { get; set; }
}
public class Bar : AbstractFoo {
public override string Title { get { return "Read Only Title"; } }
}
public class Program
{
public static void Main()
{
var foo = new Foo();
Console.WriteLine(foo.Title);
var bar = new Bar();
Console.WriteLine(bar.Title); // output: "Read Only Title"
bar.Title = "A new and better title!"; // Why does this not generate an error?
Console.WriteLine(bar.Title); // output: "Read Only Title" - value was not set
}
}
潜在的问题是,是否可以防止bar.Title
被set
,即仅该派生类的只读字段? 我在其他相关答案中看到new
关键字可用于重新声明派生类中的属性,但同时建议不要使用它。
您没有覆盖它使其只读。 您只需覆盖get
,并将set
作为基本实现。 如果你想让它生气:
public new string Title => "Read Only Title";
现在它知道一个问题。
您似乎不了解接口、实现和重新定义(新)的工作原理。
因此,很快,您无法更改该属性以使其成为只读。
使用“new”关键字,您可以强制 Bar 类型的对象具有只读的 Title 属性。 但是,如果有人将对象强制转换为接口呢? 在这种情况下,Title 属性是接口(读/写)之一,而不是 Bar 类(只读)。
只是为了用一个例子来解释它。 假设您以这种方式更改条形码:
public class Bar : AbstractFoo
{
public new string Title { get { return "Read Only Title"; } }
}
通过这种方式,您是说 Bar 类具有一个属性,该属性将覆盖 AbstractFoo 类的 Title 属性。
但是,看看这段代码:
var bar = new Bar();
bar.Title = "John"; // Compile error: Bar Title is readonly
var fooInterface = bar as IFoo;
fooInterface.Title = "Doe"; // OK -> IFoo interface Title is writable
Console.WriteLine(fooInterface.Title); // Doe -> Title is the one of the interface, not of the Bar class
如你看到的:
我希望很清楚
.NET 中的属性实际上是get
方法和set
方法的组合。 尽管这两种方法彼此密切相关,但有时只覆盖一个方法(更典型的是“set”方法)可能是有意义的。 例如,一个基本的“可观察对象”类可能有一个 get 和 set 方法,可以简单地读取或写入一个私有字段,但派生类可能会在不改变 getter 行为的情况下从 setter 中有效地引发事件。
尽管在不更改 setter 的情况下更改 getter 是不寻常的,但在类具有指示其值是否可以通过引用修改、可以保证永远不会更改或两者都不更改的情况下,以及基类不允许修改但不保证不变性。 在这种情况下,默认设置器会抛出NotSupportedException
; 派生类可能合理地使用专门的 getter,例如懒惰地创建其值,但仍使用默认的 setter。
一切都取决于您需要做什么。
通常人们定义接口来编写一种他们必须遵守的契约。 通过这种方式,您可以编写适用于订阅该合约(接口)的任何类型的对象的代码。
看看你的例子,我不明白你为什么创建一个具有读/写访问权限的接口,然后你要求将它用作只读。
无论如何,您有三个选择:
这里是最后一个选项的代码:
/// <summary>
/// Readeable Foo
/// </summary>
public interface IReadonlyFoo : ICommonFoo
{
string Title { get; }
}
/// <summary>
/// Readeable and writeable Foo
/// </summary>
public interface IFoo : ICommonFoo
{
string Title { get; set; }
}
/// <summary>
/// Common interface between readonly and complete Foo
/// </summary>
public interface ICommonFoo
{
string Name { get; set; }
}
/// <summary>
/// Abstract implementation of common properties/functions
/// </summary>
public abstract class AbstractCommonFoo : ICommonFoo
{
public string Name { get; set; }
}
/// <summary>
/// Abstract implementation of the writeable Foo
/// </summary>
public abstract class AbstractFoo : AbstractCommonFoo, IFoo
{
public string Title { get; set; }
}
/// <summary>
/// Abstrac implementation of the readonly Foo
/// </summary>
public abstract class AbstractReadonlyFoo : AbstractCommonFoo, IReadonlyFoo
{
public string Title { get; }
}
/// <summary>
/// Foo
/// </summary>
public class Foo : AbstractFoo
{
}
/// <summary>
/// Readonly Foo (Bar)
/// </summary>
public class Bar : AbstractReadonlyFoo
{
}
如果你真的需要同时拥有只读和可写两种情况,也许这对你来说是最好的。 所以在你的代码中,任何方法都会接受:
使用这两个接口会使您的代码更长,但更易于使用。 例如,在需要设置 Title 的方法中,您将参数放入 IFoo 接口,因此如果有人尝试传递 Bar,编译器会显示错误。 如果您使用“throw NotImplementedException”(其他解决方案之一),您的代码将编译,但在运行时会失败。
这不是一个简单的话题。 我希望我以一种好的方式解释它。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.