如何实现只读属性


78

我需要在我的类型上实现一个只读属性。此外,此属性的值将在构造函数中设置,并且不会被更改(我正在编写一个类,该类公开用于WPF的自定义路由UI命令,但这无关紧要)。

我看到两种方法:

  1. class MyClass
    {
        public readonly object MyProperty = new object();
    }
    
  2. class MyClass
    {
        private readonly object my_property = new object();
        public object MyProperty { get { return my_property; } }
    }
    

由于所有这些FxCop错误都表明我不应该拥有公共成员变量,因此第二个似乎是正确的方法。正确?

在这种情况下,“仅获取”属性和“只读”成员之间有什么区别吗?

我将不胜感激任何意见/建议/等。


30
有时我希望自动属性语法包括一个get; readonly set;选项。
丹·布莱恩特


@DanBryantget; private set;至少有。
Marc.2377

2
@ Marc.2377,实际上,他们{get;}不久前添加了支持,从而解决了此问题。
丹·布莱恩特

@DanBryant啊,的确如此。我应该先阅读答案;)
Marc.2377

Answers:


50

版本控制:
如果您仅对源代码兼容性感兴趣,我认为这没有多大区别。
使用属性更好地实现二进制兼容性,因为您可以将其替换为具有setter的属性,而不必破坏所编译库的代码,具体取决于您的库。

约定:
您正在遵循约定。在这种情况下,按照惯例,两种可能性之间的差异相对较小。基于反射的代码可能会再次咬住您。它可能只接受属性,而不接受字段,例如属性编辑器/查看器。

序列化
从字段更改为属性可能会破坏许多序列化程序。而且AFAIKXmlSerializer只会序列化公共属性,而不会序列化公共字段。

使用自动财产
另一个常见的变化是使用带有私有设置器的自动财产。尽管这是一个简短的属性,但它不强制执行只读性。所以我更喜欢其他的。

只读字段是自记录
的,但是该字段有一个优点:
一眼就能清楚地看到公共接口实际上是不可变的(禁止反射)。而对于属性,您只能看到无法更改它,因此您必须参考文档或实现。

但老实说,由于我很懒,我在应用程序代码中经常使用第一个。在图书馆中,我通常会更全面,并遵循惯例。

C#6.0添加了只读自动属性

public object MyProperty { get; }

因此,当您不需要支持较旧的编译器时,可以拥有一个真正的只读属性,其代码与只读字段一样简洁。


1
目前,我希望添加C#9public type prop => get;
NetMage,

65

第二种方法是首选。

private readonly int MyVal = 5;

public int MyProp { get { return MyVal;}  }

这将确保MyVal只能在初始化时分配(也可以在构造函数中设置)。

正如您已经指出的那样,您不会公开内部成员,而是允许您将来更改内部实现。


谢谢。第一个选项还可以确保相同。您认为这是首选选项的原因是什么?
akonsu 2010年

有什么我们不应该使用的理由public int MyProp { get; private set; }吗?我知道它不是真正的只读,但是非常接近。
Mike Hofer,2010年

3
@Mike Hofer-由于int被声明为readonly,因此您不能在构造函数之外进行更改。
Oded

5
@Mike Hofer-这实际上取决于意图...我写的版本公开了一个内部成员,其值在初始化后不能更改。您暴露了一个成员,其值在初始化后可能会更改。确实取决于您想要的... Mine是只读的,例如,在初始化后根本无法更改,您的是任何外部类的只读。
Oded

1
@Oded-我可以接受。这是一个细微但重要的区别。我可以看到,如果您想公开一个在内部和外部都表现为常量的属性,它将在什么地方有用。我的肯定不会那样做。
Mike Hofer,2010年

48

随着C#6的引入(在VS 2015中),您现在可以具有get-only自动属性,其中的隐式后备字段是readonly(即,可以在构造函数中分配值,而不能在其他地方分配值):

public string Name { get; }

public Customer(string name)  // Constructor
{
    Name = name;
}

private void SomeFunction()
{
    Name = "Something Else";  // Compile-time error
}

现在,您还可以内联初始化属性(带或不带setter):

public string Name { get; } = "Boris";

再次提到这个问题,这为您提供了选项2的优点(公共成员是属性,而不是字段),并且选项1简短。

不幸的是,它不能在公共接口级别上提供不变性的保证(就像@CodesInChaos关于自我文档的观点一样),因为对于该类的使用者而言,没有设置器与拥有私有设置器是没有区别的。


有关新语法的详细信息,但是,您可以反映和激活私有设置程序和/或将值加载到备用字段中(与访问修饰符无关)。也可以区分私有设置程序和运行时缺少设置程序的情况时间使用反射。
肖恩·威尔逊

1
@Shaun:很好-反射可以做很多事情!许多可能性可能与原始程序员甚至语言设计师的意图背道而驰,但是您是说readonly可以使用反射来更改字段(我不知道答案,但这似乎有问题)?
鲍勃·萨默斯

2
我并不是特别要说不是,但答案是:是的。可以。gist.github.com/wilson0x4d/a053c0fd57892d357b2c如果您认为这有问题,请耐心等待,直到您了解到地球上的每个操作系统都有一种机制,该机制可以使一个进程读取/写入任何其他进程的内存(具有足够的执行特权,这就是为什么这是为什么没有基于软件的系统能够真正地安全的原因,但是,我离开了,这与原始问题并没有太大关系:),即使它很有趣!
肖恩·威尔逊

我建议阅读详细的文章以及BillWagner的文章
hastrb

12

你可以这样做:

public int Property { get { ... } private set { ... } }

8
是的,您可以,但是使用这种技术只能保证该类的使用者不能修改该属性,而不能保证该属性在对象的生存期内保持不变。
鲍勃·萨默斯

Bob:如果该属性不能被类的使用者修改,则该属性为“ readOnly”,不是吗?
El Bayames

@ElBayames您不能更改引用,但可以更改内部构造。考虑何时公开具有内部状态的对象。您可以轻松地使用公开的get方法检索对象,然后更改该对象的内部属性。那不是真正的只读。
马修·S

5

我同意第二种方法是可取的。该首选项的唯一真实原因是.NET类没有公共字段的普遍首选项。但是,如果该字段是只读的,那么除了缺乏与其他属性的一致性之外,我看不到会有什么真正的异议。只读字段和get-only属性之间的真正区别在于,只读字段可确保其值在对象的生命周期内不会发生变化,而get-only属性不会发生变化。


4

由于封装,第二种方法是优选的。您当然可以将readonly字段设为public,但这与C#习惯用法相反,在C#习惯用法中,您可以通过属性而不是通过字段进行数据访问。

其背后的原因是该属性定义了一个公共接口,并且如果该属性的支持实现发生了变化,那么您就不会破坏其余的代码,因为该实现隐藏在接口的后面。



1

在C#9中,Microsoft将引入一种新的方法来使用以下init;方法仅在初始化时设置属性:

public class Person
{
  public string firstName { get; init; }
  public string lastName { get; init; }
}

这样,您可以在初始化新对象时分配值:

var person = new Person
{
  firstname = "John",
  lastName = "Doe"
}

但是稍后,您将无法更改它:

person.lastName = "Denver"; // throws a compiler error
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.