为什么在设置只读的覆盖 属性 时没有编译器错误?
Why is there no compiler error when setting an overridden property that is read-only?
我一直在学习 C# 继承和接口 (1, 2, 3)。在下面的示例中,为什么在派生 class 中设置 属性 时没有编译器错误,这使 属性 通过自动属性变为只读?
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
,即仅派生的 class[=25] 的只读字段=]?我在其他related answers中看到可以使用new
关键字重新声明派生class中的属性,但同时建议不要这样做。
您没有覆盖它而使其成为 read-only。您只需 仅覆盖 get
,并将 set
作为基本实现。如果你想让它生气:
public new string Title => "Read Only Title";
现在它知道一个问题。
看来你不明白接口、实现和重新定义(新)是如何工作的。
所以,很快,您无法将 属性 更改为只读。
使用“new”关键字,您可以强制 Bar 类型的 object 具有只读标题 属性。但是,如果有人将 object 强制转换为界面怎么办?在这种情况下,标题 属性 是界面之一 (Read/Write) 而不是栏 class (只读)之一。
举个例子说明一下。
假设您以这种方式更改条形码:
public class Bar : AbstractFoo
{
public new string Title { get { return "Read Only Title"; } }
}
通过这种方式,您是说 Bar class 有一个 属性 将覆盖 AbstractFoo class 的 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
如您所见:
- 如果你用Barclass,你不能写Title属性(你想要什么)
- 如果您将 object 转换为界面,它可以与 AbstractFoo 属性 一起使用...我不确定您是否想要那个
我希望清楚
.NET 中的 属性 实际上是 get
方法和 set
方法的组合。尽管这两种方法彼此密切相关,但有时只重写一个方法(更典型的是“set”方法)可能是有意义的。例如,一个基本的“可观察对象”class 可能有一个 get 和 set 方法来简单地读取或写入一个私有字段,但是派生的 class 可能有用地从 setter 引发一个事件不改变 getter 行为。
尽管更改 getter 而不更改 setter 是不寻常的,但在 class 具有指示其值是否可以修改的属性的情况下,这可能是有意义的通过引用,可以保证永远不会改变,或者两者都不改变,并且基础 class 不允许修改但不保证不变性。在这种情况下,默认的 setter 会抛出一个 NotSupportedException
;派生的 class 可能会合理地使用专门的 getter,例如懒惰地创建它的值,但仍然使用默认值 setter.
一切取决于你需要做什么。
通常人们定义接口是为了编写一种他们必须遵守的契约。
通过这种方式,您可以编写适用于订阅该合同(接口)的任何类型的 object 的代码。
看你的例子,我不明白你为什么创建一个具有 read/write 访问权限的接口,然后你要求以只读方式使用它。
无论如何,你有三个选择:
- 更改栏 class 以使用 get/set 方法获得标题 属性,但当有人设置值时抛出异常(如 supercat 之前所说)
- 把Barclass改成我之前写的,但是要注意把object投到界面的情况(我之前解释过)
- 分离只读和可写接口
这里是最后一个选项的代码:
/// <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
{
}
如果你真的需要同时拥有只读和可写的情况,也许这最适合你。所以在你的代码中,任何方法都将接受:
- ICommonFoo 当不需要 Title 时(所以你可以同时传递 Foo 和 Bar class)
- IReadonlyFoo 当Title 只读时(可以同时传递Foo 和Bar class)
- 需要设置Title时IFoo(可以只传Foo)
使用这两个接口可以使您的代码更长,但更易于使用。
例如,在您需要设置标题的方法中,您将参数放入 IFoo 接口,因此如果有人试图传递一个 Bar,编译器会显示错误。
如果您使用“抛出 NotImplementedException”(其他解决方案之一),您的代码将编译,但会在运行时失败..
这不是一个简单的话题。希望我能好好解释一下。
我一直在学习 C# 继承和接口 (1, 2, 3)。在下面的示例中,为什么在派生 class 中设置 属性 时没有编译器错误,这使 属性 通过自动属性变为只读?
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
,即仅派生的 class[=25] 的只读字段=]?我在其他related answers中看到可以使用new
关键字重新声明派生class中的属性,但同时建议不要这样做。
您没有覆盖它而使其成为 read-only。您只需 仅覆盖 get
,并将 set
作为基本实现。如果你想让它生气:
public new string Title => "Read Only Title";
现在它知道一个问题。
看来你不明白接口、实现和重新定义(新)是如何工作的。
所以,很快,您无法将 属性 更改为只读。
使用“new”关键字,您可以强制 Bar 类型的 object 具有只读标题 属性。但是,如果有人将 object 强制转换为界面怎么办?在这种情况下,标题 属性 是界面之一 (Read/Write) 而不是栏 class (只读)之一。
举个例子说明一下。 假设您以这种方式更改条形码:
public class Bar : AbstractFoo
{
public new string Title { get { return "Read Only Title"; } }
}
通过这种方式,您是说 Bar class 有一个 属性 将覆盖 AbstractFoo class 的 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
如您所见:
- 如果你用Barclass,你不能写Title属性(你想要什么)
- 如果您将 object 转换为界面,它可以与 AbstractFoo 属性 一起使用...我不确定您是否想要那个
我希望清楚
.NET 中的 属性 实际上是 get
方法和 set
方法的组合。尽管这两种方法彼此密切相关,但有时只重写一个方法(更典型的是“set”方法)可能是有意义的。例如,一个基本的“可观察对象”class 可能有一个 get 和 set 方法来简单地读取或写入一个私有字段,但是派生的 class 可能有用地从 setter 引发一个事件不改变 getter 行为。
尽管更改 getter 而不更改 setter 是不寻常的,但在 class 具有指示其值是否可以修改的属性的情况下,这可能是有意义的通过引用,可以保证永远不会改变,或者两者都不改变,并且基础 class 不允许修改但不保证不变性。在这种情况下,默认的 setter 会抛出一个 NotSupportedException
;派生的 class 可能会合理地使用专门的 getter,例如懒惰地创建它的值,但仍然使用默认值 setter.
一切取决于你需要做什么。
通常人们定义接口是为了编写一种他们必须遵守的契约。 通过这种方式,您可以编写适用于订阅该合同(接口)的任何类型的 object 的代码。
看你的例子,我不明白你为什么创建一个具有 read/write 访问权限的接口,然后你要求以只读方式使用它。
无论如何,你有三个选择:
- 更改栏 class 以使用 get/set 方法获得标题 属性,但当有人设置值时抛出异常(如 supercat 之前所说)
- 把Barclass改成我之前写的,但是要注意把object投到界面的情况(我之前解释过)
- 分离只读和可写接口
这里是最后一个选项的代码:
/// <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
{
}
如果你真的需要同时拥有只读和可写的情况,也许这最适合你。所以在你的代码中,任何方法都将接受:
- ICommonFoo 当不需要 Title 时(所以你可以同时传递 Foo 和 Bar class)
- IReadonlyFoo 当Title 只读时(可以同时传递Foo 和Bar class)
- 需要设置Title时IFoo(可以只传Foo)
使用这两个接口可以使您的代码更长,但更易于使用。 例如,在您需要设置标题的方法中,您将参数放入 IFoo 接口,因此如果有人试图传递一个 Bar,编译器会显示错误。 如果您使用“抛出 NotImplementedException”(其他解决方案之一),您的代码将编译,但会在运行时失败..
这不是一个简单的话题。希望我能好好解释一下。