Last time, I introduced the self-assignment principle for Windows Runtime properties:
- Setting a property to its current value is legal and has no effect.
One corollary to this was that setting a property twice to the same value has no effect.
A more interesting corollary is this one:
- The default value of a property must be a legal value.
If the property value has never been changed from the default, then a self-assignment will assign the default value, and that must succeed.
This second corollary can catch some people who submit API designs in which they propose something like this:
The
DoodadÂFinderclass contains a collection of properties that you can set to narrow the search that occurs when you callFind(). If you want to find Doodads with a specific partner widget, you can set theDoodadÂOptions.property to the handle of that partner widget. You may not set thePartnerÂWidget PartnerÂWidgetproperty to null.
I ask them, “So if I create a brand new DoodadÂFinder object and immediately read the PartnerÂWidget property, what do I get?”
“Oh, since this is a brand new DoodadÂFinder object, no partner widget has been specified, so the PartnerÂWidget is null.”
I pointed out that this violates the “allow self-assignment” rule:
var finder = new DoodadFinder(); finder.PartnerWidget = finder.PartnerWidget; // throws InvalidArgumentException?
You can say that a property may not be null, but you can’t say that and simultaneously default the value to null. You’re saying that the default value is illegal.
The DoodadÂFinder should allow the PartnerÂWidget to be set to null if it is already null.
Indeed, I think that they should allow it to be set back to null, so that there’s a way to undo a prior set. As long as you undo it before calling Find(), then it should behave as if it had never been set.
var finder = new DoodadFinder(); ConfigureFinder(finder); // might set PartnerWidget finder.PartnerWidget = null; // cancel the partner widget filter
or even
void ConfigureFinderButPreservePartnerWidget(DoodadFinder finder)
{
var originalPartner = finder.PartnerWidget;
try {
ConfigureFinder(finder); // might set the PartnerWidget
} finally {
finder.PartnerWidget = originalPartner;
}
}
Next time, we’ll look at another perhaps-surprising corollary to the self-assignment principle.
It boggles my mind, what is the point of doing value validation if you don’t have valid defaults… For those objects with too many properties, this screams builder/factory pattern.
I’ve done a few “Default Value Not Legal” versions in the past. The only ones I could justify were of this form:
The object had a large number of properties; too many for a constructor to be wieldy. (Think GetSaveFileName’s structure for how many.)
A number of properties had to be initialized for the object to work. The programmer was supposed to immediately initialize them on creating the object before trying to use it.
The default property value was NULL.
NULL wasn’t a legal value.
I've been the maintenance programmer on a project that did that, and the only way to tame the bug list after a few years of rapid codebase growth was to use a builder pattern to make the constructor wieldly. You could create a builder with all of the properties set to NULL (and, indeed, could restore them to NULL in the builder), but until they were all non-NULL, there was no way to get the final object out of the builder; the only other way to build the final object was to call the full constructor with ~100 parameters set...
I agree with this principle, but I think there may be a more general way of framing it, that captures a larger space of possible pitfalls:
A class that has a property named X must be a behavioral subtype of an otherwise-identical class that has a public field named X. That is, if you have some code that works correctly when the property is a public field, that code should also work correctly when the property is a property.
Obviously, there will be some pathological cases that you don't have to support. Code that would inevitably fail is not "correct," so it...