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ÂFinder
class 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ÂWidget
property 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.
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 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...