7
\$\begingroup\$

This is supposed to be an immutable structure representing a time-stamped value. Since it represents a physical measurement, it cannot change, and I want to make sure it really doesn't change accidentally at runtime.

public struct Sample
{
    public TimeSpan Time { get { return _time; } }
    private readonly TimeSpan _time;

    public Double Value { get { return _value; } }
    private readonly Double _value;

    public Sample(TimeSpan time, Double value) : this()
    {
        _time = time;
        _value = value;
    }
}

I have heard a lot about "properties are better than fields", and it's common to see the following idiom:

public Double Foo { get; private set; }

But I wonder:

  1. Does it make a difference? I feel I prefer the current, field-based form, but maybe I am missing something...
  2. In case it does, which one is preferrable, regarding immutability (and also other characteristics)?
\$\endgroup\$
3

4 Answers 4

6
\$\begingroup\$

Thought I'd chime in:

You've stated that this should be an immutable struct. With that in mind, the solution you want is the one with the backed readonly fields.

I feel explicitly stating intent in code is good. You say that you want immutability, then write the code so.

If you were to opt for the property only solution then consider what would happen if some other developer came along and misunderstood your original intent (because this time it wasn't fully explicit)

With the properties you are stating - The public consumers of my struct may not mutate it but the struct itself can.

With the readonly fields you are stating - The public consumers of my struct may not mutate it and neither may the struct itself.

The latter is correct in regards to your spec.

To briefly summarise:

"Does it make a difference?" Yes, your intent is different between the two. Yes, one allows mutation after the point of declaration, one does not.

"In case it does, which one is preferrable, regarding immutability (and also other characteristics)?" For immutability, the readonly option.

\$\endgroup\$
2
  • \$\begingroup\$ Hi, welcome to CodeReview. Thank you for your answer! \$\endgroup\$
    – Abbas
    Dec 17, 2014 at 9:54
  • \$\begingroup\$ These are straightforward and compelling points! Thank you! \$\endgroup\$ Dec 17, 2014 at 12:38
4
\$\begingroup\$

Using the readonly keyword ensures that the value can only be set as part of the declaration

private readonly bool value = true;  

or in the constructor of the same class.

A private set can be called from anywhere in the class. So one could say it is safer to use the readonly if you need to ensure that the value won't be changed anywhere (by mistake e.g).

Using the private set aproach will lead to less code and in increased readability.

\$\endgroup\$
2
\$\begingroup\$

"properties are better than fields" only says that you should prefer public properties over public fields. It does not say that auto properties are better than properties with explicit backing fields.

  • Readonly backing fields have stronger immutability guarantees
  • Auto properties require a call to :this() in the constructor. This is a bit ugly and potentially reduces performance a bit.
  • Auto properties are less code

Personally I use auto properties if I'm lazy, but readonly backing fields when I aim for good code.


I believe C# 6 will add a feature for "readonly properties" that combines the advantages of these alternatives.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Nice point on :this() construct. I actually removed it. Think I'm going to stick to readonly fields. \$\endgroup\$ Dec 16, 2014 at 13:32
2
\$\begingroup\$

Class properties have significant advantages over class fields, and a class which exposes autoproperties rather than fields can easily be changed to use "normal" properties later without affecting any of the calling code. Structure properties do not offer those same advantages, however. The only times a struct property has a real advantage over a structure field are when the property is defined as doing something that could not be accomplished by reading a field (in which case an auto-property wouldn't be suitable), when the structure needs to implement an interface that exposes a property, or when the struct has to be usable with code that accesses properties via Reflection.

A structure is, fundamentally, a collection of independent variables stuck together with duct tape. Given variables X and Y of some structure type, the statement X=Y will mutate X by overwriting all of its instance fields with the values of the corresponding fields in Y. This mutation is done without regard for whether any of the fields are public or private, read-only or mutable, and there's no way the code for a struct can do anything about it. If a structure allows all its fields to be read, and allows the creation of a struct instances with any combination of field values, then the structure will be semantically equivalent to a structure which simply exposes its fields, and it may as well do so. Only if a structure has private fields whose values cannot be observed, or imposes restrictions on the values fields can take, is it helpful to do anything else. Otherwise, the fact that struct fields may be overwritten outside the struct's control limits the value of making structures pretend to be immutable.

\$\endgroup\$
6
  • \$\begingroup\$ Thanks for writing! These are interesting considerations, but what is the conclusion? What "should I do"? After reading your answer I'm still (more?) confuse... \$\endgroup\$ Dec 16, 2014 at 19:13
  • \$\begingroup\$ @heltonbiker: Unless your structure encapsulates behavior beyond holding a bunch of independent-but-related variables, or one of the other indicated conditions applies, simply use exposed public fields. In the absence of restrictions on what the fields may contain, I wouldn't bother making them readonly, since I consider the notion of an "immutable struct" to be a lie. If a variable or property of structure type is read-only, all its fields will be read-only regardless of how they are declared; if a variable of structure type is writable and there are no restrictions... \$\endgroup\$
    – supercat
    Dec 16, 2014 at 19:26
  • \$\begingroup\$ ...on what they may contain, then all fields of the struct instance held in that variable will be mutable as noted above, again regardless of how they are declared. Sometimes structures are used as a form of "lightweight object" with semantics beyond a bunch of variables stuck together with duct tape; structures which are used in that way should be immutable. If, however, a structure is being used as an aggregation of variables rather than as an object, then exposing writable public fields is a very clear way of advertising such intention. \$\endgroup\$
    – supercat
    Dec 16, 2014 at 19:30
  • 1
    \$\begingroup\$ @heltonbiker: The fact that today at 12:34:56am a particular sensor happened to read 2.125 may be fixed in history which one would need a time machine to alter, but nothing would prevent the creation of a Sample which held the DateTime value above but a some other arbitrary double value. As such, the Sample type doesn't really represent "the value that was read at a particular time". Instead, if code says Sample someSample = GetNextReading();, the relationship between the field values will be established in the GetNextReading() method. Making struct fields readonly is... \$\endgroup\$
    – supercat
    Dec 16, 2014 at 20:26
  • 1
    \$\begingroup\$ ...better than using autoproperties, but to my eye doing either would imply that the struct itself is responsible for establishing invariants related to those fields. In many cases where code which is going to return a structure becomes aware of field values individually, I prefer to build a structure by setting the fields individually, even if the recipient is unlikely to alter them after that. If you prefer always to use the constructor, that's your prerogative; I might not favor such judgment, but would suggest readonly fields are better than properties in that case. \$\endgroup\$
    – supercat
    Dec 16, 2014 at 20:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.