MSBuild Task Generator: Part 7. Rendering a loop in the CodeDOM.

For those that asked – on a Friday “tomorrow” means Monday. J

 

And a few people have asked “what does this have to do with MSBuild” – yeah, I suppose I’m a bit off topic but I’m trying to lay a little foundation so that when I’m talking about the task generator I don’t feel compelled to explain the CodeDOM and can instead focus on the task generation.

 

Last time we talked about generating a conditional statement – today we’ll look at rendering a simple loop.  We’ll create an array of strings and then iterate over them and print out their values.  Simple, right?  Well … yeah!

 

First we need an array of strings and the initializer string.  It might not be obvious at first but when creating an expression such as “MyType foo = bar” we need to define “bar” first.  I want to create the following statement:

 

string[] colors = new string[] { "Red", "Green", "Blue"};

 

So first we need to create the array initializer expression as such:

 

CodeArrayCreateExpression colorsInit =

      new CodeArrayCreateExpression(

            typeof(string),

            new CodeExpression[] {

      new CodePrimitiveExpression("Red"),

                  new CodePrimitiveExpression("Green"),

                  new CodePrimitiveExpression("Blue")

            }

      );

 

 

And then create a variable (colors) that uses that initializer expression:

 

CodeVariableDeclarationStatement colors = new CodeVariableDeclarationStatement(

typeof(string[]),

"colors",

colorsInit);

 

This renders the following:

 

string[] colors = new string[] {

        "Red",

        "Green",

        "Blue"};

 

Now we want a simple loop that does the following:

 

foreach (string s in colors)

{

      Console.WriteLine(s);

}

 

The problem is that we are using the CodeDOM and are trying to be language agnostic, right?  And “foreach” does not exist on every platform and therefore the CodeDOM doesn’t really know how to render that.  It does, however, know how to render a “for” loop.  So let’s shoot for something more like:

 

for (int index = 0; index < colors.Length; index++)

{

      Console.WriteLine(colors[index]);

}

 

First let’s declare that indexer (index) and assign it to an initial value of 0:

 

string indexName = "index";

CodeVariableDeclarationStatement index = new CodeVariableDeclarationStatement(

                                                typeof(int),

                                                indexName,

                                                new CodePrimitiveExpression(0)

                                          );

 

That will be the initializer of our for-loop.  Now we need the test condition (index < colors.Length):

 

CodePropertyReferenceExpression colorsLength = new CodePropertyReferenceExpression(

                                                      new CodeVariableReferenceExpression(colorsName),

                                                      "Length");

CodeBinaryOperatorExpression indexLTLength = new CodeBinaryOperatorExpression(

      new CodeVariableReferenceExpression(indexName),

                                                      CodeBinaryOperatorType.LessThan,

                                                      colorsLength);

 

Third we need the loop iteration statement (index++).  It turns out that this is not going to use the “++” operation and instead render something more like:  index = index + 1;   Much like not rendering a “foreach” this is not unexpected.

 

CodeAssignStatement indexIncrement = new CodeAssignStatement(

                                          new CodeVariableReferenceExpression(indexName),

                                          new CodeBinaryOperatorExpression(

      new CodeVariableReferenceExpression(indexName),

            CodeBinaryOperatorType.Add,

                                                new CodePrimitiveExpression(1)

                                          )

                                    );

 

And lastly we need the loop body.  This involves an array indexer and a method call.  We will need to wrap all of this in a CodeStatement array since the CodeIterationStatement requires that:

 

CodeArrayIndexerExpression colorsAtIndex = new CodeArrayIndexerExpression(

                                                new CodeVariableReferenceExpression(colorsName),

                                                new CodeExpression[] {

                                                      new CodeVariableReferenceExpression(indexName)

                                                }

                                          );

CodeTypeReferenceExpression console = new CodeTypeReferenceExpression(typeof(Console));

CodeMethodReferenceExpression consoleWriteLine = new CodeMethodReferenceExpression(console, "WriteLine");

CodeMethodInvokeExpression performWriteLine = new CodeMethodInvokeExpression(

                                                      consoleWriteLine,

                                                      new CodeExpression[] {

      colorsAtIndex

                                                      }

                                                );

CodeStatement[] loopBody = new CodeStatement[] {

      new CodeExpressionStatement(performWriteLine)

};

 

Finally we can now create out iteration statement (whew!):

 

CodeIterationStatement forColors = new CodeIterationStatement(

                                          index,

                                          indexLTLength,

                                          indexIncrement,

                                          loopBody);

 

 

And when we render the array initializer “colors” and the loop “forColors” (along with the namespace and class stubs we learned about earlier – all in a method named “PrintLoop”):

 

namespace CodeDOMSample

{

    using System;

   

   

    public class LoopSample

    {

       

        public virtual void PrintLoop()

        {

            string[] colors = new string[] {

                    "Red",

                    "Green",

                    "Blue"};

            for (int index = 0; (index < colors.Length); index = (index + 1))

            {

                System.Console.WriteLine(colors[index]);

            }

        }

    }

}

 

Seems like an awful lot of work to get some basic C# code rendered, right?  Yeah, but by doing it this way we are able to pull out our code generator and drop in a new one.  The following is the result of generating in VB:

 

Imports System

Namespace CodeDOMSample

    Public Class LoopSample

        Public Overridable Sub PrintLoop()

            Dim colors() As String = New String() {"Red", "Green", "Blue"}

            Dim index As Integer = 0

            Do While (index < colors.Length)

                System.Console.WriteLine(colors(index))

                index = (index + 1)

            Loop

        End Sub

    End Class

End Namespace

 

And any other language with a ICodeGenerator implementation could also be supported.

 

So that’s a basic introduction to using the CodeDOM.  Tomorrow we’ll start talking about the MSBuild task generator (and about time, too).