【问题标题】:How to implement a read only property如何实现只读属性
【发布时间】:2011-04-24 11:39:27
【问题描述】:

我需要在我的类型上实现一个 只读 属性。此外,此属性的值将在构造函数中设置并且不会更改(我正在编写一个公开 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 错误都说我不应该有公共成员变量,看来第二个是正确的方法。对吗?

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

如果有任何 cmets/建议/等,我将不胜感激。

【问题讨论】:

  • 我有时希望自动属性语法包含get; readonly set; 选项。
  • @DanBryant 至少有get; private set;
  • @Marc.2377,其实他们不久前就增加了对{get;}的支持,解决了这个问题。
  • @DanBryant 啊,确实。我应该先阅读答案;)

标签: c# properties readonly


【解决方案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

【讨论】:

  • init 属性的支持字段也是readonly
【解决方案2】:

C# 6.0 添加只读自动属性

public object MyProperty { get; }

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


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

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

序列化
从字段更改为属性可能会破坏很多序列化程序。而 AFAIK XmlSerializer 只序列化公共属性而不是公共字段。

使用自动属性
另一个常见的变体是使用带有私有设置器的自动属性。虽然这很短并且是一个属性,但它并不强制只读。所以我更喜欢其他的。

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

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

【讨论】:

  • 目前我希望添加 C# 9 public type prop => get;
  • 但是 getter only 属性和 readonly 属性是完全不同的。 readonly 类字段通常用于在类构造期间初始化的变量,以后永远不会更改。简而言之,如果您需要确保您的属性值永远不会从外部更改,但您需要能够从您的类代码内部更改它,请使用“仅获取”属性。
【解决方案3】:

另一种方式(我最喜欢的),从 C# 6 开始

private readonly int MyVal = 5;

public int MyProp => MyVal;

https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/properties#expression-body-definitions

【讨论】:

  • 不能简化为一行public int MyProp { get; } = 5;?
【解决方案4】:

随着 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 关于自我文档的观点),因为对于该类的消费者而言,没有设置器与拥有私有接口没有区别二传手。

【讨论】:

  • 关于新语法的好信息,但是,您可以反映和激活私有 setter 和/或将值加载到支持字段中(无论访问修饰符如何)。也可以区分私有 setter并且在运行时缺少使用反射的设置器。
  • @Shaun:好点子——你可以用反射来做很多的事情!许多可能性可能违背原始程序员甚至语言设计者的意图,但您是说readonly 字段可以使用反射来更改(我不知道答案,但似乎有问题)?
  • 我并没有特别说,不,但答案是:是的......你可以。 gist.github.com/wilson0x4d/a053c0fd57892d357b2c 如果您认为这是有问题的,请等到您了解地球上的每个操作系统都有一种机制,一个进程可以读取/写入任何其他进程的内存(即,给定足够的执行权限。)这是为什么没有基于软件的系统可以真正安全,但是,我离题了,这与最初的问题没有太大关系:)即使它很有趣!
  • 我建议您也阅读详细文章BillWagner's article
【解决方案5】:

你可以这样做:

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

【讨论】:

  • 是的,你可以,但是使用这种技术,你只能保证属性不能被类的消费者修改,而不是在对象的生命周期内保持不变。
  • Bob:如果类的消费者不能修改该属性,那么该属性是“只读”的,不是吗?
  • @ElBayames 您不能更改参考,但可以更改内部结构。考虑何时公开具有内部状态的对象。您可以轻松地使用公开的 get 方法检索对象,然后更改该对象的内部属性。这不是真正的只读。
【解决方案6】:

我同意第二种方式更可取。这种偏好的唯一真正原因是 .NET 类没有公共字段的普遍偏好。但是,如果该字段是只读的,除了与其他属性缺乏一致性之外,我看不出会有任何真正的反对意见。 readonly 字段和 get-only 属性之间的真正区别在于,readonly 字段保证其值在对象的生命周期内不会改变,而 get-only 属性则不会。

【讨论】:

    【解决方案7】:

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

    这背后的原因是该属性定义了一个公共接口,如果该属性的支持实现发生更改,您最终不会破坏其余代码,因为该实现隐藏在接口后面。

    【讨论】:

      【解决方案8】:

      第二种方式是首选。

      private readonly int MyVal = 5;
      
      public int MyProp { get { return MyVal;}  }
      

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

      正如您所指出的 - 这样您就不会暴露内部成员,从而允许您在将来更改内部实现。

      【讨论】:

      • 谢谢。第一个选项也确保相同。您认为这是首选选项的原因是什么?
      • 我们有什么理由不应该使用public int MyProp { get; private set; }?我知道它不是真正的只读,但它非常接近。
      • @Mike Hofer - 由于 int 被声明为 readonly,因此您无法在构造函数之外更改它。
      • @Mike Hofer - 这真的取决于意图是什么......我写的版本公开了一个内部成员,其值在初始化后无法更改。你的暴露了一个成员,其值在初始化后可能会改变。真的取决于你想要什么......我的是只读的,就像在初始化后根本无法更改,你的是只读的,就像任何外部类一样。
      • @Oded - 我可以接受。这是一个微妙但重要的区别。如果您想公开一个在内部和外部都表现为常量的属性,我可以看到它在哪里有用。我的肯定不会那样做。
      猜你喜欢
      • 2023-03-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多