MSBuild Task Generator: Part 9. GenerateField and generating array initializers in the CodeDOM.

GenerateField is pretty straight-forward .. here, just look:

 

CodeMemberField GenerateField(MBFTaskProperty tp)

{

      string privateName = string.Format("m_{0}", tp.Name);

      CodeMemberField cmf = new CodeMemberField(tp.Type, privateName);

cmf.Attributes = MemberAttributes.Private;

      if (StringHasData(tp.Default))

      {

            if (tp.IsArray)

            {

                  cmf.InitExpression = new CodeArrayCreateExpression(tp.Type, tp.DefaultExpressionArray);

            }

            else

            {

                  cmf.InitExpression = tp.DefaultExpression;

            }

      }

     

      return cmf;

}

 

So we simply create the field (prepending a “m_” – the property and field will otherwise have the same name), mark it private and give it the Type defined in the ITaskProperty member.  You can read about ITaskProperty in Part 4 of this series.

 

The only really interesting part here is the initializer expressions in the case where a Default is specified in the ITaskProperty.  There are two cases, the array and the non-array case.

 

The array case is far more interesting (and explains the non-array case).  In the case of an array we need to generate a field with assocaited array initializer syntax

 

So GenerateField calls MBFTaskProperty.DefaultExpressionArray (this is why in yesterday’s column we demanded that the ITaskProperty implementer be an MBFTaskProperty instance).  DefaultExpressionArray basically splits the semi-colon deliminated default value into an array of the basic type.  Basically the following pseudo code:

 

CodeExpressionCollection ceItems;

foreach(item in Default) {

    TargetType instance = createInstanceOfTargetType(item);

    ceItems.Add(new CodePrimitiveExpression(instance));

}

return ceItems;

 

But how does createInstanceOfTargetType work?

 

We know the type of the target object (both as an enumeration and also as a System.Type), and we know the value we are converting from.  Most of the objects are IConvertible so we can simply use Convert.ChangeType(item, TargetType).  But there are several that are not IConvertible.  The general algorithm is to determine if the type is IConvertible (via a switch), and if so use that interface.  However if not then a call to Parse is generated to actually create an instance of the object at runtime.  That looks like this:

 

private CodeExpression CallParse(System.Type t, string val)

{

      CodeTypeReferenceExpression ctre = new CodeTypeReferenceExpression(t);

      CodeMethodReferenceExpression parse = new CodeMethodReferenceExpression(ctre, "Parse");

      return new CodeMethodInvokeExpression(

parse,

new CodeExpression[] {

new CodePrimitiveExpression(val)

}

);

}

private CodeExpression DefaultExpression_Internal(PropertyType type, string val)

{

      object o = null;

      CodeExpression ce = null;

      switch (type)

      {

            case PropertyType.ITaskItem:

                  throw new ArgumentException("Can not create a default expression for an ITaskItem");

            case PropertyType.SystemDateTime:

                  ce = CallParse(typeof(DateTime), val);

                  break;

            case PropertyType.SystemSByte:

                  ce = CallParse(typeof(System.SByte), val);

                  break;

            case PropertyType.SystemString:

                  o = val;

                  break;

            case PropertyType.SystemUInt16:

                  ce = CallParse(typeof(System.UInt16), val);

                  break;

            case PropertyType.SystemUInt32:

                  ce = CallParse(typeof(System.UInt32), val);

                  break;

            case PropertyType.SystemUInt64:

                  ce = CallParse(typeof(System.UInt64), val);

                  break;

            default:

                  o = Convert.ChangeType(val, UnarrayType);

                  break;

      }

      if (ce == null)

      {

            ce = new CodePrimitiveExpression(o);

      }

      return ce;

}

 

Incidentally this method is also what is used internally in the non-array case (tp.DefaultExpression) (remember that we call this in a loop in the array case).  So let’s look at an example:  This input:

 

<?xml version="1.0" encoding="utf-8" ?>

<Tasks>

      <Task Namespace="ArrayTest" Class="Test1">

            <Property Name="StringArray" Type="System.String"

IsArray="true" Default="Value1;Value2;Value3"/>

            <Property Name="IntArray" Type="System.Int32"

IsArray="true" Default="10;20;30;40"/>

            <Property Name="DoubleArray" Type="System.Double"

IsArray="true" Default="10.50;20.75;30.10;40.0001"/>

            <Property Name="DateTimeArray" Type="System.DateTime" IsArray="true"

Default="2/16/1992 12:15:12;2/16/1992 12:15:13;2/16/1992 12:15:14"/>

      </Task>

</Tasks>

 

Generates the following fields (properties are removed to keep the post length down):

 

// Auto-generated Task: 3/17/2004 10:24:51 AM

namespace ArrayTest

{

    using System;

    using Microsoft.Build.Framework;

    using Microsoft.Build.Utilities;

    using MBFTaskUtils;

   

   

    public abstract class Test1 : Microsoft.Build.Utilities.Task

    {

       

        #region StringArray

        private string[] m_StringArray = new string[] {

                "Value1",

                "Value2",

                "Value3"};

       

        #endregion

     

        #region IntArray

        private int[] m_IntArray = new int[] {

                10,

                20,

                30,

                40};

       

        #endregion

       

        #region DoubleArray

        private System.Double[] m_DoubleArray = new System.Double[] {

                10.5,

                20.75,

                30.1,

                40.0001};

       

        #endregion

       

        #region DateTimeArray

        private System.DateTime[] m_DateTimeArray = new System.DateTime[] {

          System.DateTime.Parse("2/16/1992 12:15:12"),

                System.DateTime.Parse("2/16/1992 12:15:13"),

                System.DateTime.Parse("2/16/1992 12:15:14")};

       

        #endregion

    }

}

 

 

So there you go.  A quick overview of the field generation algorithm.  Nothing fancy.

 

By the way a GDN workspace is open where I am posting the project as it evolves.  Since it is only prototype code don’t expect much in the way of comments and great design – but over time it will improve.  This is blog-quality code J

 

Lots of known bugs BTW – some bugs, some design issues.  Some we’ll tackle on this blog, some not.

 

No warranty expressed or implied.  YMMV.