Want to show your appreciation?
Please a cup of tea.

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