I'm forced to get into the code generation business when I had to generate the Web Service client ourselves for a reason that I'll blog later. While CodeDom API is every powerful but all those CodeXXX classes quickly made my code generator program a monster.
This in turn forced me into creating a utility class and it did help to have my generator code organized. Now I can write below in one line:
CodeUtils.DefineClass(TypeAttributes.Public, "MyClass", baseType);
Here it is the utility class source code:
/// <summary>
/// Utility methods for generating source code using CodeDom.
/// </summary>
/// <author>Kenneth Xu</author>
public static class CodeUtils
{
/// <summary>
/// Single dimension array access expression.
/// </summary>
/// <param name="array">The array.</param>
/// <param name="index">The index.</param>
/// <returns>
/// An instance of <see cref="CodeArrayIndexerExpression"/>
/// </returns>
public static CodeArrayIndexerExpression ArrayIndex(
CodeExpression array, int index)
{
return new CodeArrayIndexerExpression(
array, new CodePrimitiveExpression(index));
}
/// <summary>
/// Determine the if a parameter is <see langword="ref"/> or
/// <see langword="out"/>.
/// </summary>
/// <param name="parameter">The parameter from reflection.</param>
/// <returns>Parameter direction</returns>
public static FieldDirection DetermineParameterDirection(
ParameterInfo parameter)
{
FieldDirection direction;
if (parameter.IsOut)
direction = FieldDirection.Out;
else if (parameter.ParameterType.IsByRef)
direction = FieldDirection.Ref;
else
direction = FieldDirection.In;
return direction;
}
/// <summary>
/// Define a new method.
/// </summary>
/// <param name="attributes">Custom attributes.</param>
/// <param name="modifier">Access modifier.</param>
/// <param name="returnType">Data type to return</param>
/// <param name="name">Name of the method.</param>
/// <param name="parameters">
/// Parameter definition of this method.
/// </param>
/// <returns>A <see cref="CodeMemberMethod"/>.</returns>
public static CodeMemberMethod DefineMethod(
CodeAttributeDeclaration[] attributes,
MemberAttributes modifier,
CodeTypeReference returnType,
string name,
params CodeParameterDeclarationExpression[] parameters
)
{
CodeMemberMethod method = new CodeMemberMethod();
method.Name = name;
method.Attributes = modifier;
method.ReturnType = returnType;
method.CustomAttributes.AddRange(attributes);
method.Parameters.AddRange(parameters);
return method;
}
/// <summary>
/// Define a local variable in a <paramref name="method"/>.
/// </summary>
/// <param name="method">
/// The method that new varaible will be defined in.
/// </param>
/// <param name="type">The type of the variable</param>
/// <param name="name">The name of the variable</param>
/// <param name="initializer">The variable initializer.</param>
/// <returns>
/// A <see cref="CodeVariableReferenceExpression"/> that can be used
/// to refer to the defined variable.
/// </returns>
public static CodeVariableReferenceExpression DefineVariable(
CodeMemberMethod method,
CodeTypeReference type,
string name,
CodeExpression initializer)
{
CodeVariableDeclarationStatement statement =
new CodeVariableDeclarationStatement(type, name, initializer);
method.Statements.Add(statement);
return new CodeVariableReferenceExpression(name);
}
/// <summary>
/// Define a parameter for the given <paramref name="method"/>.
/// </summary>
/// <param name="method">The method to define the paramter.</param>
/// <param name="type">The type of the parameter.</param>
/// <param name="name">The name of the parameter.</param>
/// <returns>
/// A <see cref="CodeVariableReferenceExpression"/> that can be used
/// to refer to defined parameter.
/// </returns>
public static CodeVariableReferenceExpression DefineParameter(
CodeMemberMethod method,
CodeTypeReference type,
string name)
{
return DefineParameter(method, type, FieldDirection.In, name);
}
/// <summary>
/// Define a parameter for the given <paramref name="method"/>.
/// Optionally the parameter can be <see langword="out"/> or
/// <see langword="ref"/>.
/// </summary>
/// <param name="method">The method to define the paramter.</param>
/// <param name="type">The type of the parameter.</param>
/// <param name="direction">To specify ref or out parameter.</param>
/// <param name="name">The name of the parameter.</param>
/// <returns>
/// A <see cref="CodeVariableReferenceExpression"/> that can be used
/// to refer to defined parameter.
/// </returns>
public static CodeVariableReferenceExpression DefineParameter(
CodeMemberMethod method,
CodeTypeReference type,
FieldDirection direction,
string name)
{
CodeParameterDeclarationExpression parameter =
new CodeParameterDeclarationExpression(type, name);
parameter.Direction = direction;
method.Parameters.Add(parameter);
return new CodeVariableReferenceExpression(name);
}
/// <summary>
/// Define a new class without custom attribute. It has no base
/// class and doesn't implement any interface.
/// </summary>
/// <param name="modifier">Access modifier</param>
/// <param name="name">Class name</param>
/// <param name="typeParameters">
/// Optional type parameter if this is a generic class.
/// </param>
/// <returns>A <see cref="CodeTypeDeclaration"/>.</returns>
public static CodeTypeDeclaration DefineClass(
TypeAttributes modifier,
string name,
params CodeTypeParameter[] typeParameters)
{
return DefineClass(
null, modifier, name, typeParameters, null, null);
}
/// <summary>
/// Define a new non-generic class with no custom attributes.
/// </summary>
/// <remarks>
/// Generic type parameters and custom attributes can be added later.
/// </remarks>
/// <param name="modifier">Access modifier</param>
/// <param name="name">Class name</param>
/// <param name="baseType">The base class.</param>
/// <param name="interfaces">Optional interfaces to implement.</param>
/// <returns>A <see cref="CodeTypeDeclaration"/>.</returns>
public static CodeTypeDeclaration DefineClass(
TypeAttributes modifier,
string name,
CodeTypeReference baseType,
params CodeTypeReference[] interfaces)
{
return DefineClass(
null, modifier, name, null, baseType, interfaces);
}
/// <summary>
/// Define a new non-generic class.
/// </summary>
/// <remarks>
/// Generic type parameters can be added later.
/// </remarks>
/// <param name="attributes">Custome attributes for the class.</param>
/// <param name="modifier">Access modifier</param>
/// <param name="name">Class name</param>
/// <param name="baseType">The base class, or null.</param>
/// <param name="interfaces">Optional interfaces to implement.</param>
/// <returns>A <see cref="CodeTypeDeclaration"/>.</returns>
public static CodeTypeDeclaration DefineClass(
CodeAttributeDeclaration[] attributes,
TypeAttributes modifier,
string name,
CodeTypeReference baseType,
params CodeTypeReference[] interfaces)
{
return DefineClass(
attributes, modifier, name, null, baseType, interfaces);
}
/// <summary>
/// Define a new class.
/// </summary>
/// <param name="attributes">Custome attributes for the class.</param>
/// <param name="modifier">Access modifier</param>
/// <param name="name">Class name</param>
/// <param name="typeParameters">
/// Type parameter if this is a generic class, null otherwise.
/// </param>
/// <param name="baseType">The base class.</param>
/// <param name="interfaces">The interfaces to implement.</param>
/// <returns>A <see cref="CodeTypeDeclaration"/>.</returns>
public static CodeTypeDeclaration DefineClass(
CodeAttributeDeclaration[] attributes,
TypeAttributes modifier,
string name,
CodeTypeParameter[] typeParameters,
CodeTypeReference baseType,
params CodeTypeReference[] interfaces)
{
return DefineType(attributes, modifier, TypeType.Class,
name, typeParameters, baseType, interfaces);
}
private static CodeTypeDeclaration DefineType(
CodeAttributeDeclaration[] attributes,
TypeAttributes modifier,
TypeType typeType,
string name,
CodeTypeParameter[] typeParameters,
CodeTypeReference baseType,
CodeTypeReference[] interfaces)
{
CodeTypeDeclaration ctd = new CodeTypeDeclaration();
ctd.Name = name;
ctd.TypeAttributes = modifier;
switch (typeType)
{
case TypeType.Class:
ctd.IsClass = true;
break;
case TypeType.Interface:
ctd.IsInterface = true;
break;
case TypeType.Struct:
ctd.IsStruct = true;
break;
case TypeType.Enum:
ctd.IsEnum = true;
break;
}
if (attributes != null)
{
ctd.CustomAttributes.AddRange(attributes);
}
if (typeParameters != null)
{
ctd.TypeParameters.AddRange(typeParameters);
}
if (baseType != null)
{
ctd.BaseTypes.Add(baseType);
}
if (interfaces != null)
{
ctd.BaseTypes.AddRange(interfaces);
}
return ctd;
}
private enum TypeType
{
Class,
Interface,
Struct,
Enum
}
}
After working with CodeDom for a while, I must say that the API was poorly designed. Although those utility methods helped a bit, but any program using this API still looks cumbersome and hard to maintain.
I'm hoping that I can have a fluent interface so that I can write like this:
DeclearType.Class.Attribute(...).Public.Virtual.Name("MyClass").Extends(baseType).Implements(...);
Utility methods for easier and better looking CodeDom program
No comments:
Post a Comment