Step 2: Enumerating Types
You may remember from earlier in the article that you need a type-discriminator so that the data layer knows which types to instantiate. Fortunately, building an enumeration type is the easiest possible non-trivial CodeDOM operationbecause really, an Enum is just a class with only data-members. You'll start to notice a theme when working with the CodeDOM namespace: structure is easier than content. And enums are all structure.
The snippet below includes all the code you need to build an enum: just loop through all of the types, and add a member for each one.
TypeCollection allTypes =
inheritanceTree.GetAllTypes();
_typeEnum = new CodeTypeDeclaration( "TypeEnum" );
_typeEnum.IsEnum=true;
for(int i = 0; i<allTypes.Count; i++)
{
Type t = allTypes[i];
CodeMemberField field = new CodeMemberField();
field.Name=t.Name;
field.InitExpression= new
CodePrimitiveExpression(i);
_typeEnum.Members.Add(field);
}
Step 3: Construction
Next, you have to write the code that outputs constructors. The default constructor is easy enough to build:
private CodeConstructor BuildDefaultCtor()
{
CodeConstructor defaultConstructor = new
CodeConstructor();
defaultConstructor.Attributes =
MemberAttributes.Private;
return defaultConstructor;
}
And here's the output:
private ContactPropertyUnion()
{
}
A non-trivial constructor takes a little more effort. The example below takes a reference to a wrappable object, and assigns it to a data member. Remember our theme: In CodeDOM, structure is easier than content. Now, we're getting into some content. This method you're trying to output has just one line of code, but writing it with CodeDom takes a dozen:
private CodeConstructor BuildWrapperCtor()
{
CodeConstructor wrapCtor = new CodeConstructor();
wrapCtor.Attributes=defaultVisibility;
//Setup the single parameter.
CodeParameterDeclarationExpression toWrapParam =
new CodeParameterDeclarationExpression();
toWrapParam.Name="toWrap";
toWrapParam.Type = new CodeTypeReference(
inheritanceTree.Root );
wrapCtor.Parameters.Add(toWrapParam);
//Assign the parameter to the data member.
CodeAssignStatement assign = new
CodeAssignStatement();
assign.Left = WrappedFieldReference;
assign.Right = new CodeVariableReferenceExpression(
toWrapParam.Name);
wrapCtor.Statements.Add(assign);
return wrapCtor;
}
Here's the output:
internal ContactPropertyUnion(Contact toWrap)
{
wrapped=toWrap;
}
Step 4: Properties
Finally, you're getting to the heart building PropertyUnion classes automatically. Essentially, every property getter has four steps:
- Initialize a default variable. (Just in case the wrapped object doesn't implement the property.)
- Check the type of the wrapped object.
- Depending on the type, cast the wrapped object to its concrete type and invoke the appropriate property on the cast object.
- Return the resulting value.
That's not too bad. These five steps contain mostly contentsuch as conditionals and property or method invocations.
Listing 2 builds an accessor based on a PropertyInfo object from the System.Reflection namespace. It's long, but at least you only have to write it once.
So that's the high-level CodeDOM picture. Of course there are some details, things like comments, and building defaults, so take a look at the
sample application for details. But most of the work takes place in the snippets above. Even more than elsewhere, keeping things simple really pays off when working with code generation.
I'm not going to tell you that the PropertyUnion is the greatest design pattern since the Singleton. It's not. But it can be a good way to centralize type-checking-ugliness in a single class, especially when you're doing ugly type-checking in different layers of your application.
The real point is this: If you can reduce a pattern down to its bare, mindless essentials, if you can simplify it until it becomes simplistic, then you can probably build a reusable implementation with code generation.