I concur with this.
@billygamesinc
Don’t be clever with properties. Use them like logical extensions to readonly or read/write fields. Typically there are only two scenarios where properties are useful: 1) input validation, or 2) state validation (with potential side-effects).
The setter in the 2nd scenario can sometimes appear complicated. In that case, promote the setter’s body to a private method so that you can keep the property as simple as possible.
float _gridSpacing;
public float GridSpacing {
get => _gridSpacing;
set => setGridSpacing(value);
}
private void setGridSpacing(float value) {
// validate/sanitize input i.e. value = Mathf.Max(0f, value);
// invalidate/process some state(s) i.e. _gridObj.Invalidate();
_gridSpacing = value;
// propagate as needed i.e. _gridObj.SetSpacing(_gridSpacing);
// yadda yadda
}
Similarly if all you want is to feature a read-only access, you can use auto-properties.
Instead of doing
public float _gridSpacing;
you do
public float GridSpacing { get; } // no backing field is needed
// + you can also initialize this by appending = 2.2; (i.e.)
If you want to internally be able to write, but still have it readonly then
public float GridSpacing { get; private set; }
That said, a property is really just a facade for a field (edit: a syntactic sugar, as stated before) with some advanced behavior attached to it, think of it like a reaction to user code trying to change a field, or a convenience of sorts. This includes getters as well.
float _angle; // private; in radians
public float Angle => _angle;
public float AngleDegrees => _angle * Mathf.Rad2Deg;
This one will escalate the situation upon detecting a change
float _gridSpacing;
public float GridSpacing {
get => _gridSpacing;
set {
if(!isSame(_gridSpacing, value, threshold: 1E-4f)) {
_gridSpacing = value;
GridChanged?.Invoke(this, _gridSpacing);
}
}
}
If your get
or set
blocks/expressions violate the nominal behavior of actually getting or setting a backing field, then you shouldn’t really use properties, although there could be some exceptions to this if this fits into the above scenarios. For example
int _ni;
public int NaturalInteger {
get => _ni;
set => _ni = max(1, value);
}
Here doing myObj.NaturalInteger = -5
would implicitly assign 1
and that’s ok by design. Maybe you don’t like this design, and would like to throw an error instead, that’s also fine.
int _ni;
public int NaturalInteger {
get => _ni;
set {
if(value <= 0) throw new ArgumentException();
_ni = value;
}
}
All of this, however, doesn’t mean the class fields are immediately rendered obsolete by properties. If you don’t mind a field being read-write and need not react to it being changed, then simply use a public field.
For more information refer to spiney’s post above.