Want to show your appreciation? Please to my charity.

Monday, August 18, 2008

Utility methods for easier and better looking CodeDom program

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