次の方法で共有


What are these "Changeable" things Avalon has, and why does it need them?

Frank Hileman wonders about the Changeable class in a comment on a usability post to Steven Clarke's blog. Here is his comment:

The Changeable class. This one really has me stumped. I can't figure out why the concept is there at all. Why simulate mutability on immutable objects? Why not just make them mutable, if that is what is needed? This looks suspiciously like over-engineering to me, or some sort of premature optimization. I doubt any users really want to learn about Changeable.EmbeddedChangeableReader. If internally, MS wants to use something like that, that is fine. How to fix it? Make the objects mutable.

I can claim responsibility for the Changeable class and pattern. The documentation on Changeable in the Longhorn SDK could do a better job of motivating why Changeable is important. I’ll work with the SDK team to see that this is improved.

Changeables are a response to the fact that there are competing requirements for many of our types. On the one hand, types should be fully mutable so they can be modified at will. On the other hand, most instances, once they’re put into use, are actually not intended to be modified.

Allowing them to remain modifiable either forces additional copies to be madeor forces them to maintain a list of all their uses. Furthermore, it forces them, since they’re mutable, to be associated with a single UIContext. However, stock objects like the stock Red Brush, Brushes.Red, needs to be immutable so it can be robustly shared between contexts.

These two requirements suggest one of two approaches. Either we have a dual set of types – one mutable and one immutable – for every type, and every API needs to be very clear on which of the two it accepts, and there are conversions between the two; or we have some sort of runtime mechanism to allow instances to either be immutable or mutable within a single type. Earlier we pursued the first approach, but the effects of doing so hugely increased the size of our API, and prevented apps from being able to poke new values deep into a type without copying the entire type. It also made simple usage of types more daunting. Example code that would do this would look like the following, where every immutable type XXX would have a dual XXXBuilder type that is mutable:

            // Create a solid color brush and use it
            SolidColorBrushBuilder scbB = new SolidColorBrushBuilder();
            scbB.Color = Colors.Red;
            SolidColorBrush scb = scbB.ToBrush();
            myElement.Background = scb;

(yes, in this case, one could consider SolidColorBrush, the immutable type, to have a constructor that takes the color, but this is just demonstrative of a problem that will still occur for more complex objects, as well as for objects that, once created, you want to change.)

Instead of this dual set of types, we have Changeable, which represents a runtime choice of mutability. The way Changeables are structured also allows for maximal sharing until the point where a change is needed, and then the minimal amount of copying occurs.

But we feel that the solution is also not to simply make the objects uniformly be mutable, since scalability is of paramount importance and mutability and sharing don’t mesh well together, particularly given the fact that most objects do not, in fact, need to be changed at runtime. (And the problem about sharing mutable objects across contexts.)

The equivalent code for doing the above in the world of Changeables is:

            SolidColorBrush scb = new SolidColorBrush();
            scb.Color = Colors.Red;
            myElement.Background = scb;

that last assignment represents a “use” of the brush, and what gets put into Background is conceptually an immutable copy (though the implementation may be more optimal than that). There are more advanced mechanisms available so the copy put in there is mutable and can be modified after the fact, but the basic pattern is what is shown here. This is based on the reality that, while all things in an app need to be able to be set up so they can be modified, the fact is that most are not actually modified. Recognition of this helps tremendously with optimizations that can be made, particularly as what is being displayed becomes richer and richer.

Regarding your point about Changeable.EmbeddedChangeableReader: I completely agree. No one wants to or should have to learn about this. This is a protected method intended for those who implement Changeable (which should be very few). We may in fact want to obfuscate this even more than having it be a protected method by moving it to a helper class for use by implementers, so it’s even less likely to be encountered by the vast majority of those who simply are clients of Changeables. There are a whole load of protected methods on Changeable that really shouldn’t need to be seen by anyone outside of the implementers of Avalon.

Comments

  • Anonymous
    December 05, 2003
    Wow, thanks for the great explanation!! This sounds like a run-time const. I like the idea of having a flag that prevents objects from being modified, especially good to fix things like Brushes.Red. These objects aside, aren't many objects going to be modified at runtime, with all the animation going on?

    Well, you guys know what is best to do internally. I see Changeable is an advanced concept, to provide a handle to something that is internally immutable, similar to String. It does seem useful for the constant brushes. I don't yet understand why it is important to share all these other things across contexts.

    It would be best if the whole concept of Changeable was completely hidden from the naive user, so they do not have to read "Using Changeable Objects". Think of the user who does not even have a good grasp on the term "immutable" -- these users are common. Changeable is a low level internal optimization detail with no perceived benefit to the user at coding time. Perhaps a combination of simplicity and optimization is not possible without CLR changes, but I can see users scratching their heads already.

    The concept could could be hidden using the traditional pattern of putting the const data in another object and using it through a thin handle object that appears mutable, except for the consts like Brushes.Red (const flag in the handle). Then make the const data type completely private. This idea must have been discussed and rejected. If you feel like commenting on that I would be interested; it is an interesting problem.