如何实现只读属性

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

我认为有两种方法:

  1. 类 MyClass
    {
    公共只读对象 MyProperty = new object () ;
    }
    
  2. 类 MyClass
    {
    Private readonly object my _ property = new object () ;
    公共对象 MyProperty { get { return my _ property; }
    }
    

所有这些 FxCop 错误都说明我不应该使用公共成员变量,似乎第二个错误才是正确的方法。对吗?

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

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

219368 次浏览

The second way is the preferred option.

private readonly int MyVal = 5;


public int MyProp { get { return MyVal;}  }

This will ensure that MyVal can only be assigned at initialization (it can also be set in a constructor).

As you had noted - this way you are not exposing an internal member, allowing you to change the internal implementation in the future.

The second method is preferred because of the encapsulation. You can certainly have the readonly field be public, but that goes against C# idioms in which you have data access occur through properties and not fields.

The reasoning behind this is that the property defines a public interface and if the backing implementation to that property changes, you don't end up breaking the rest of the code because the implementation is hidden behind an interface.

I agree that the second way is preferable. The only real reason for that preference is the general preference that .NET classes not have public fields. However, if that field is readonly, I can't see how there would be any real objections other than a lack of consistency with other properties. The real difference between a readonly field and get-only property is that the readonly field provides a guarantee that its value will not change over the life of the object and a get-only property does not.

You can do this:

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

C# 6.0 adds readonly auto properties

public object MyProperty { get; }

So when you don't need to support older compilers you can have a truly readonly property with code that's just as concise as a readonly field.


Versioning:
I think it doesn't make much difference if you are only interested in source compatibility.
Using a property is better for binary compatibility since you can replace it by a property which has a setter without breaking compiled code depending on your library.

Convention:
You are following the convention. In cases like this where the differences between the two possibilities are relatively minor following the convention is better. One case where it might come back to bite you is reflection based code. It might only accept properties and not fields, for example a property editor/viewer.

Serialization
Changing from field to property will probably break a lot of serializers. And AFAIK XmlSerializer does only serialize public properties and not public fields.

Using an Autoproperty
Another common Variation is using an autoproperty with a private setter. While this is short and a property it doesn't enforce the readonlyness. So I prefer the other ones.

Readonly field is selfdocumenting
There is one advantage of the field though:
It makes it clear at a glance at the public interface that it's actually immutable (barring reflection). Whereas in case of a property you can only see that you cannot change it, so you'd have to refer to the documentation or implementation.

But to be honest I use the first one quite often in application code since I'm lazy. In libraries I'm typically more thorough and follow the convention.

With the introduction of C# 6 (in VS 2015), you can now have get-only automatic properties, in which the implicit backing field is readonly (i.e. values can be assigned in the constructor but not elsewhere):

public string Name { get; }


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


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

And you can now also initialise properties (with or without a setter) inline:

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

Referring back to the question, this gives you the advantages of option 2 (public member is a property, not a field) with the brevity of option 1.

Unfortunately, it doesn't provide a guarantee of immutability at the level of the public interface (as in @CodesInChaos's point about self-documentation), because to a consumer of the class, having no setter is indistinguishable from having a private setter.

yet another way (my favorite), starting with C# 6

private readonly int MyVal = 5;


public int MyProp => MyVal;

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

In C# 9 Microsoft will introduce a new way to have properties set only on initialization using the init; method like so:

public class Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
}

This way, you can assign values when initializing a new object:

var person = new Person
{
Firstname = "John",
LastName = "Doe"
}

But later on, you cannot change it:

person.LastName = "Denver"; // throws a compiler error