Vital Techniques for Using Objects as Properties

Explains critical undocumented design issues of building components with objects as properties

Introduction

Our experiences with Visual Studio 2005™ demonstrate that function calls are not fired in outer set accessors of properties subject to TypeConverters. This article first revisits how to deploy TypeConverters so that your class properties will be displayed from a nested node in Properties View. It then demonstrates the necessary pattern for declaring DefaultValueAttributes, and for writing set accessors that will successfully fire vital accessor functions.

Background

A rudimentary and very common chore of writing class libraries is defining properties comprised of custom classes or structs. As with Font, Size, Location, or Anchors, a developer will practically always want to adhere to conventions which display the class/struct property in an expandable property node at design time.

This is a job we should be able to get done in just a few minutes. Product documentation and nomenclature however make it difficult to find reference material on nested node behavior, and fail altogether to explain what constraints are imposed on DefaultValueAttributes or what hurdles obstruct functions we expect to perform in property accessors. You can spend days on these regular design issues instead of minutes without the simple instructions which follow.

This example exposes a ColorOffset property for a custom button class, producing the following nested node and reset behavior in the IDE.

"TypeConverters" Provide Nested Node Behavior

Thanks to very unhelpful documentation, many developer's nested node behavior efforts might remain dead in the water until they discover material such as a Code Project article, "Creating Custom Controls-Providing Design Time Support 1" by Kodanda Pani. Pani explains that we have to build a TypeConverter for the class of our property, overriding GetPropertiesSupported( ) to return true; and overriding GetProperties( ) to pass the class of our property to the base implementation. The latter reflects our subproperties to the nested node. This is all there is to the necessary TypeConverter:

In the Outer Class, Declare the Property *Without a DefaultValueAttribute*

To our great displeasure however, and at incredible wastes of work, we found that acceptable IDE behavior was impossible if we declared DefaultValueAttributes on property declarations in outer classes. DefaultValueAttributes would compile, and they might even work once, but thereafter reset behavior would crash; property accessors would fail to read or write, and so forth.

Exhaustive experimentation found that DefaultValueAttributes could only be declared directly on the bottom/inner-most property class members (GCColorOffset.BaseProperty). Almost certainly however, good reasons will compel you to declare DefaultValueAttributes on the outer class property, because it is the outer class to which the default is associated, and because usual conditions require a given DefaultValue in one outer class while yet other outer classes require different DefaultValues. As these are standard patterns which class designs must contend with, forcing us to declare DefaultValueAttributes on the inner property class denies us the DefaultValue flexibility that class library designs require.

Costs Of Discovering You Cannot Declare DefaultValueAttributes in The Outer Class

It is practically impossible therefore that class design efforts will never encounter this issue. Because composite classes are inevitable design paths, this flaw or weakness of the IDE will cost all class engineers dearly, as we first struggle for great whiles to make DefaultValueAttributes work in outer classes, only to find after exhaustive wasted efforts that the IDE blows itself out of the water if we do so.

That this weakness is not documented — even by a recommended pattern for declaring DefaultValueAttributes — only compounds our injuries, because we can only solve this obstructive behavior by conceding to a pattern which is adverse to inevitable purposes of class design. Needing to associate DefaultValues with the outer class, every temptation to place them in inner classes will be strenuously resisted. Why? Because you will want to avoid an obvious further problem (and bad design) this imposes: Your further class design efforts will suffer the further cost of having to subclass inner property classes merely to declare different defaults.

Costs Of Discovering That Vital Functions Are Never Fired in Outer Class Accessors

In the very next moments of this inevitable process however, we discover a further amazing thing — that vital function calls in outer class property accessors are not fired by the IDE. Indeed, design time execution of set accessors simply jumps across vital instructions as if they are meant to be ignored. Ignored calls are preserved below:

In other words, at design time, outer class accessors of properties subject to TypeConverters can perform no validation, no function calls... you get the picture. Thus, you will have to perform vital accessor functions too from the same improbable and unreasonable place — the inner property class. *Further* exhaustive (wasted) experimentation found that outer class design time handling supports no more than the following (most simple possible) accessor form:

C# Example

Revising Inner Classes to Fire Obligatory Outer Class Functions

Secondly, we must revise both property class and outer class designs so that vital accessor functions of the outer class are fired from the property class.

This purpose is reasonably accomplished with delegates or references. Because this example requires the reference for other purposes which are not evident from this example, and because we do not have to expose this property class elsewhere, we first show how to use a reference to the outer class. To preempt potential calls to a null reference, we assign the reference as early as possible with a specialized constructor for the property class:

When the outer class creates an instance of the property class, of course it passes this to the specialized property class constructor:

f_ColorOffset = new GCColorOffset( this );

Having called such a constructor so, accessors of our property class can make the calls into the outer class functions which the IDE ignored when we made them where they belong. We now call them (absurdly) from the outer class's set accessor:

This reference or delegate pattern solves our problems and demonstrates that there's just a bit more to every little composite property than might meet the eye. Of course, when/if the IDE is ultimately repaired, successful designs will be achieved as easily as they should have been from the beginning — eliminating the temporary need for these workarounds, and requiring that you revise your class designs (again) to your original intention.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

Comments and Discussions

I am still a .net newbie moving to Visual Studio but too come from a background in component design (in win32 Delphi). I agree with your article up to the point where you pass the surrounding object (the button) to the constructor of the inner object (the GCColorOffset) and then call methods of the surrounding object within the inner object. This tightly couples the two classes together which means if you decide to write a label control that has a similar ColorOffset property, you'll have to refactor your ColorOffset class to support your label too.

What could you do to get around this? Well you could have the button/label class implement an IColorOffsetChangeListener interface and pass this down to the ColorOffset object but what would be even simpler (imho) is firing an event when the ColorOffset class changes.

The button/label/etc would just hook this event and refresh their appearance when fired. This way there's no two-way tight-coupling between the two classes so ColorOffset class need not know who is using it and why they're using it.

First of all, I *never* need to re-surface this property in another class, because it is declared in the root class. Another thing we usually want to do when we write an article, is avoid complicating it with further ramifications. So, closely coupling the ColorOffset class is in fact the most efficient arrangement.

Otherwise I would agree, and I don't understand why you would assume the contrary from any of the material. Neither of your suggestions is simpler, and at best they only accomplish the same purpose with baggage quite redundant for the cited purposes (which do not include any need whatsoever to deploy ColorOffset in further classes).

but what would be even simpler (imho) is firing an event when the ColorOffset class changes.

Actually, you are right here. I have further purposes for requiring the reference, and the two approaches are roughly equivalent. But yes, if we don't need the further reference, delegating the event will work very well.

I was hoping my final revisions to this article could have been posted instead of this original verion. Nonetheless, I've re-submitted the final revs, noting your suggestion and giving you credit for it, so that you can receive all the troll hate mail instead of me!

Just kidding (about the last part).

Delphi Win32. Boy am I sorry to have all the work I have there ripped from under me.

I do get your point about doing it the simplest way possible to make it easy to understand. I guess when writing a tutorial it's often a balancing act of demonstrating something using the simplest code that's easiest to understand OR using more complicated code which might be a better practice.

In my opinion, having loosely coupled code is a good OOP practice and I thought using an event would be worth the benefit (if only for teaching people a better practice to walk away with) for the small amount of extra code you'd write. But I'm anal like that!

I do apologize if I offended you - my comment was meant to be constructive criticism and I can't say that my way is better...but I agree with me, lol. I did notice that post by 'reggingsucks' and hoped my comments wouldn't be thrown in the same negative basket as his (sounds like he can't let go of the eighties! ). Aside for this small point I do agree with the rest - good article.

You're right. But not always, because if there's a bit of overhead you don't need, then you're wasting resources. That's why I'm anal the other way. (You may be too, actually.)

In building this property, I absolutely considered using a delegate. But there's a bit more to that than using a reference; and because I need the reference, and because I didn't need that minute little bit more, I eliminated it. Simple as that. Keep it simple... you know the rest.

Anyway, no, you are totally right for your purposes, and I do apologize in return, because I must admit, I wanted to choke the brat dead on the spot, and I was feeling pretty hot. I guess that's what really sick little people want.

As far as the article quality goes, I appreciate the compliment but take no pride (and considerable embarrassment) from the quality. You can write something out of great difficulty, get that distilled pretty well (you think), and wake up tomorrow, give it a look, and think you've made yourself look pretty foolish.

Anyway, no damage. Thanks for the apologies, and the suggestion. Hope you can accept mine in return.

Since when is it bad or even unavoidable practice to use objects as properties? You mean we can simply forego Style, Font, Anchors and so forth, and this "does NOT solve biz problems, it just duplicates them programmatically"?

I never heard anything so ludicrous in a long while. How would you write such a property? I don't think you have the slightest idea what you're pretending to be an expert on. After all, if there were a better way, that would be standard practice, and we would have no Fonts, Styles, Size, Location, or Anchors because that just gets in the way of your biz logic?

And what ever gave you the idea that is "procedural" programming. A property *itself* is a departure from procedural programming.

Back to the peanut gallery with you -- or furnish some code of your ostensibly better way, or examples of just how such properties "get in the way of biz logic."

Already have, Norm. And I understand where you're coming from there. Dead horse, and we're never looking back.

I'm not sold on .Net, but C# is a thing of beauty... so we have one foot in the C++ pond and the other here. This article (I mean to touch it up to the finished copy on our pages) is a consequence of conquering a couple of unexpected hurdles, and, as it states, because these issues are so commonplace, I thought it would be of substantial service.

Frankly, as to the previous post, you just have to wonder sometimes why to try to do anything for anyone any more. If there were half a wit to the denunciation, they'd know better than to type the first letter if they can't back it up. To be so much more a poet than a real codesmith is too obvious. Are there really people who are so ignorant that they think they can throw some utterly unqualified purely philosophical denunciation at people who have written millions of lines of fine code in many development tools -- and worked hard to find the answers to the problems addressed here?