Properties? Not my bag, baby.
Eric recently posted a blog: “Property or backing store from inside a class?”, and I responded in a way that skirted the issue entirely. I want to discuss them in a little more detail.
Part 1: Why bother?
First, why do we use properties? The most common answer I hear is “future-proofing”. Specifically, people are concerned that they may need to change the behavior of a property, and don’t want to break existing consumers.
I understand the concern, but I don’t subscribe to the practice 100%:
- YAGNI – Most of the properties I write, I won’t need. They just go to waste. It’s PrematureGeneralization.
- I’ll probably build some other part of my API in a way that I have to break the contract when I update, so the properties won’t help.
- If my class has default accessibility (internal), then it’s trivial to recompile every consumer, so no future proofing is necessary.
- It takes a simple idea (a field in a class) and makes it take 6 lines. This clutters my code.
- It’s not the Simplest Thing That Could Possibly Work.
If you’re a library vendor, things are a bit different. You’re selling upgradeability, as well as functionality.
Most of this is inspired by a blurb in Ron Jeffries’ article, Adventures in C#: Contiguous Owners , at the very bottom of the document. He, in turn, was inspired by Ward Cunningham.
Part 2: a simple approach
Just use public fields.
Wait, doesn’t that violate Encapsulation? Only a little bit. If you say “I have a public property call Foo, and its type is int”, that’s not much different than “I have a public field call Foo, and its type is int”. What are you encapsulating, exactly?
Wait, doesn’t that open a risk that someone might set the value, when they shouldn’t? Yeah, but it doesn’t scare me much. I rarely find myself writing code that manipulates the values in other objects. It’s bad behavior on my part, and rarely necessary. If it does happen, and it’s a problem later, I will fix it then.
Wait, I’m still worried about rogue setters. Fine, use ‘readonly’. I do:
public readonly int Foo; // note that it's named like a property.
public C(int foo)
{
this.Foo = foo;
}
Now Foo acts like a property with only a getter, and has the same syntax. If, someday, I decide it needs to be a property, I don’t have to modify my consumers. (I do have to rebuild them if they’re in a different assembly.)
I now don’t have to worry about accidentally changing a value, even in my own code.
Also, I’ve noticed a certain pattern. Typically my fields are either “identity of the class” or “working data of the class”. I can see which is which by marking the “identity” fields readonly.
Part 3: lazy load
Sometimes you want to calculate something on-demand, and then cache the value. For example, you might write:
class Size
{
public readonly uint Width;
public readonly uint Height;
bool _haveCalculatedArea = false;
uint _area;
public Size(uint width, uint height)
{
this.Width = width;
this.Height = height;
}
public uint Area
{
get
{
if (!this._haveCalculatedArea)
{
this._area = this.Height * this.Width;
this._haveCalculatedArea = true;
}
return this._area;
}
}
If you take this approach, you now need to be careful to use the property inside your class, to work with the correct semantics.
I had said I would extract a new class encapsulate this behavior. Here’s what that might look like:
class Size
{
public readonly uint Width;
public readonly uint Height;
public readonly LazyArea Area;
public Size(uint width, uint height)
{
this.Width = width;
this.Height = height;
this.Area = new LazyArea(this);
}
}
class LazyArea
{
readonly Size _size;
bool _haveCalculated = false;
uint _value;
public LazyArea(Size size) { this._size = size; }
public uint Value
{
get
{
if (!this._haveCalculated)
{
this._value = this._size.Height * this._size.Width;
this._haveCalculated = true;
}
return this._value;
}
}
}
Note: I’m violating the Law of Demeter by exposing the Area as public. Consumers must use “mySize.Area.Value”. I think I should probably make it private, and use a public field:
readonly LazyArea _area;
public uint Area { get { return this._area.Value; } }
Part 4: Lazy<T>
I’m thinking I could write a generic class to implement this type of caching in a general way. Do you want to take a stab at it, and post your result in a comment or on your own blog?