On of the Windows Runtime API design principles is that reading a property¹ or adding an empty event handler should not affect the API’s proper usage. It is legal for the implementation to optimize based on whether a property was accessed or whether a handler is registered,² but the optimization should not affect overall correctness.
Here are examples of bad behavior we want to avoid:
If you read the
Widget.Stream
property, you must call theClose
method on the returned stream.
If you add a handler for the
FancyReady
event, then thePlainReady
event is not raised.
The
MischiefDetected
event handler must callMischiefManaged
before returning.
The reason for the “reading a property should not affect proper usage” guideline is that many debuggers will “helpfully” dump the properties of an object. In the case of the Stream
property above, if reading the Stream
creates an obligation to Close
it, then each time you hover over a widget or log it to the console, the debugger will read the Stream
property and show it on the screen. The debugger doesn’t know the special rule about having to Close
the stream, so the stream will go unclosed, and you have a memory leak.
Even worse, that stream may be associated with an open file handle, so now you leaked a file handle, and the effects of a leaked file handle can be quite severe. Debugging is hard enough. Don’t create a situation where a bug is introduced by the presence of a debugger. “Yeah, we can’t run this program under the debugger to figure out what is going wrong, because once we run it under the debugger, it crashes with a sharing violation.”
It is also common, especially when learning how to use a new feature, to add handlers to every event, where all the handler does is log a message like “FancyReady received” followed by the values of all of the event arguments. This lets developers see the event flow and gain a better mental model of how the feature works. But if adding an event handler changes the feature’s proper behavior, you create a version of the Heisenberg Uncertainty Principle: Attempting to observe the system changes its behavior.
And you definitely don’t want to put people into a position where they throw up their hands in frustration and say, “I don’t understand. Once we connect a debugger or turn on logging, the program crashes even before we get to the problem we’re trying to solve. This problem is un-debuggable.”
¹ You are allowed to raise an exception from a property access if the situation calls for it.
² You are allowed to require that a handler be registered for an event. That doesn’t violate the principle, because you’re saying that omitting the handler is was never proper API usage to begin with. (In C/C++ terms, it is “undefined behavior”.) It does mean that if the developer adds a dummy handler that just logs information but does no work, they might inadvertently “fix” their program. In the case of improper usage, you should pass a custom message to RoÂOriginateÂError
to remind the developer why the operation failed. “You must register a MuffinReady handler before you can Bake().”
What design decisions can lead to violation of this principle?
>If you read the Widget.Stream property, you must call the Close method on the returned stream.
This slightly confuses me. In WinRT (or COM in general), if you get a property of object type (whose return type is pointer to IUnknown or its descendant), then you’re of course supposed to Release it. I suppose the principle does not apply to this case, as any decent debugger should be aware of this situation. (Also a property returning...
It happens when people implement the Stream getter as
IStream Stream { get { return new Stream(); } }
. They aren’t thinking of a property as “tell me the value of this thing”, but rather as a handy syntax for function calls.Perhaps you're working on a legacy system that has a vast C API, and Widget.Stream is a property provided for backward compatibility with that C API - it actually returns a "stream handle" that's just a bare pointer to some opaque structure plopped into a chunk of memory that the property getter allocated just for you. To work with it, you use a bunch of C functions, and when you're done, you close it by...
C# has one of these baked into the language.
When an event has no subscribers, it is equal to null, so you need to have a null check before invoking the event, which introduces a flow divergence from the case where you do have subscribers.
You cannot invoke an event — what people are often doing is to invoke the delegate stored in the underlying field of a field-like event, which is internal to the class, as the implementation of raising that event. The principle does not suggest that C# should ensure a field-like event is backed by a non-null delegate. Instead, it means that the implementing class must raise the event correctly with the null check, given how C#...
You are talking about the consequences of those side effects while a debugger is active, or when you are learning the API. Which is fine. But I think there is a more fundamental justification for this principle.
A property is a pair of getter and setter in disguise which makes it look and act as if it were a member variable. Thus, just as reading a variable should have no side effects at all, reading a...
These days, it's mostly something you run into with embedded systems, but there's a long tradition of hardware registers that are reset-on-read. They generally serve a specific purpose along the lines of "what has changed since the last time I asked?" which turns out to be a convenient way to avoid the potential race condition that happens when read and reset are separate operations and there's no such thing as locking.
I agree that it's probably...
Oh, yes. Hardware registers. Lovely stuff, filled with features whose only purpose seems to cause trouble. But those have the excuse of being one-off interfaces, often used just by the driver provided by the maker. Anyway, even when I'm designing an interface just for my use, I tend to avoid those gotchas. I have been bitten enough times. In fact, there is this user interface design adage, "if the user needs to look at the...