如何实现只读属性

ako*_*nsu 73 c# properties readonly

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

我认为有两种方法可以做到:

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

由于所有这些FxCop错误都说我不应该有公共成员变量,所以第二个似乎是正确的方法.正确?

在这种情况下,get only属性和只读成员之间是否有任何区别?

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

Ode*_*ded 62

第二种方式是首选方案.

private readonly int MyVal = 5;

public int MyProp { get { return MyVal;}  }
Run Code Online (Sandbox Code Playgroud)

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

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

  • @Mike Hofer - 这实际上取决于意图是什么......我写的版本暴露了一个内部成员,其值在初始化后无法改变.您的公开了一个成员,其值可能在初始化后发生变化.真的取决于你想要的东西...我的只读,就像在初始化之后根本无法改变,你的只读,就像在任何外部阶级一样. (5认同)
  • @Mike Hofer - 由于int被声明为_readonly_,因此无法在构造函数之外更改它. (3认同)

Cod*_*aos 49

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

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

序列化
从字段到属性的更改可能会破坏许多序列化程序.AFAIK XmlSerializer只会序列化公共属性而不是公共字段.

使用Autoproperty
另一个常见的Variation是使用带私有设置器的autoproperty.虽然这很短并且属性不会强制执行readonlyness.所以我更喜欢其他的.

Readonly字段是自我记录
虽然该字段有一个优点:
它使公共界面一目了然,它实际上是不可变的(禁止反射).而在属性的情况下,您只能看到无法更改它,因此您必须参考文档或实现.

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

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

public object MyProperty { get; }
Run Code Online (Sandbox Code Playgroud)

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


Bob*_*ers 43

随着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
}
Run Code Online (Sandbox Code Playgroud)

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

public string Name { get; } = "Boris";
Run Code Online (Sandbox Code Playgroud)

回到这个问题,这给了你选项2的优点(公共成员是属性,而不是字段),选项1的简洁.

不幸的是,它没有在公共接口层面提供不可变性的保证(如@ CodesInChaos关于自我文档的观点),因为对于类的消费者而言,没有setter与拥有私有的setter无法区分.

  • 我没有特别说,不,但答案是:是的......你可以.https://gist.github.com/wilson0x4d/a053c0fd57892d357b2c如果您认为这有问题,请等到您了解到地球上的每个操作系统都有一个机制,其中一个进程可以读取/写入任何其他进程的内存(给定足够的执行权限,即.)这就是为什么没有基于软件的系统可以真正安全,但是,我离题,这对原始问题没有太大作用:)即使它很有趣! (2认同)

Nob*_*ody 11

你可以这样做:

public int Property { get { ... } private set { ... } }
Run Code Online (Sandbox Code Playgroud)

  • 是的,你可以,但是使用这种技术,你只能保证该类的消费者不能修改该属性,而不是它在对象的生命周期内保持不变. (8认同)

own*_*wns 7

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

private readonly int MyVal = 5;

public int MyProp => MyVal;
Run Code Online (Sandbox Code Playgroud)

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

  • 不能简化为一行 `public int MyProp { get; } = 5;` ? (4认同)

Mek*_*ebo 7

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

public class Person
{
  public string FirstName { get; init; }
  public string LastName { get; init; }
}
Run Code Online (Sandbox Code Playgroud)

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

var person = new Person
{
  Firstname = "John",
  LastName = "Doe"
}
Run Code Online (Sandbox Code Playgroud)

但后来,你不能改变它:

person.LastName = "Denver"; // throws a compiler error
Run Code Online (Sandbox Code Playgroud)

  • “init”属性的支持字段也是“readonly” (2认同)

Eri*_*sen 5

我同意第二种方式更可取.这种偏好的唯一真正原因是一般偏好.NET类没有公共字段.但是,如果该字段是只读的,我看不出会有比缺乏与其他属性一致的其他任何真正的反对.一个只读字段,并得到只读属性之间的真正区别是,只读域提供了保证,它的值不会超过对象的生活和只得到属性更改不会.