C#, immutability and public readonly fields
I have read in many places that exposing fields publicly is not a good idea, because if you later want to change to properties, you will have to recompile all the code which uses your class.
However, in the case of immutable classes, I don't see why you would ever need to change to properties - you're not going to be adding logic to the 'set' after all.
Any thoughts on this, am I missing something?
Example of the difference, for those who read code more easily than text :)
//Immutable Tuple using public readonly fields
public class Tuple<T1,T2>
{
public readonly T1 Item1;
开发者_StackOverflow社区 public readonly T2 Item2;
public Tuple(T1 item1, T2 item2)
{
Item1 = item1;
Item2 = item2;
}
}
//Immutable Tuple using public properties and private readonly fields
public class Tuple<T1,T2>
{
private readonly T1 _Item1;
private readonly T2 _Item2;
public Tuple(T1 item1, T2 item2)
{
_Item1 = item1;
_Item2 = item2;
}
public T1 Item1 { get { return _Item1; } }
public T2 Item2 { get { return _Item2; } }
}
Of course, you could use auto-properties (public T1 Item1 { get; private set; }
), but this only gets you 'agreed immutability' as opposed to 'guaranteed immutability'...
C# 6.0 now supports auto-property initializers.
The auto-property initializer allows assignment of properties directly within their declaration. For read-only properties, it takes care of all the ceremony required to ensure the property is immutable.
You can initialize read-only properties in constructor or using auto-initializer
public class Customer
{
public Customer3(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
public string FirstName { get; }
public string LastName { get; }
public string Company { get; } = "Microsoft";
}
var customer = new Customer("Bill", "Gates");
You can read more about auto-property initializers here
It is an obvious omission from properties that you cannot write something like:
public T2 Item2 { get; readonly set; }
I'm not even sure readonly
is the best word to use to mean "can only be set in the constructor", but that's what we're stuck with.
This is actually a feature that many people have requested, so let's hope that it will be introduced in a hypothetical new version of C# some time soon.
See this related question.
You may not need to add any logic to a setter in the future, but you may need to add logic to a getter.
That's a good-enough reason enough for me to use properties rather than exposing fields.
If I'm feeling rigorous then I'd go for full immutability (explicit readonly
backing fields with exposed getters and no setters). If I'm feeling lazy then I'd probably go for "agreed immutability" (auto-properties with exposed getters and private setters).
In C#9 we have the init accessor which you could use instead of the set accessor.
var firstCar = new Car { Color = "Orange", Brand = "Mclaren" };
public class Car
{
public string Color { get; init; }
public string Brand { get; init; }
}
An init only property (or indexer) is declared by using the init accessor in place of the set accessor
An instance property containing an init accessor is considered settable in the following circumstances, except when in a local function or lambda:
- During an object initializer
- During a with expression initializer
- Inside an instance constructor of the containing or derived type, on this or base
- Inside the init accessor of any property, on this or base
- Inside attribute usages with named parameters
Init only setters
As a standard practice I follow your 2nd example only using 'readonly' when the object is public or vulnerable to inadvertent tampering. I am using the 'agreed immutability' model in a current project building a plugin framework. Obviously, with agreed immutability, the readonly
protection is removed.
Only in rare circumstances do I expose a field - public, internal, or otherwise. It just doesn't feel right unless writing a property {get;} takes more time than I'm willing to give.
The idea behind properties is that, even if you don't intend to chang them now or later, mabye you might need to in some unforseen way. Let's say you need to change the getter to do some kind of calculation, or logging. Maybe you need to add exception handling. Lots of potential reasons.
Also consider semantics. if T1 is a value type rather than a reference type, then accessing obj.Item1 returns a copy of _Item1 in the getter, while accessing Item1 without a getter would not retrieve a copy. This means that while Item1 may be immutable internally, the returned value type object isn't. I can't think of a reason why that would be a good thing, but it is a difference.
精彩评论