WindowsDevCenter.com
oreilly.comSafari Books Online.Conferences.

advertisement


AddThis Social Bookmark Button

Using the CodeDOM

by Nick Harrison
02/03/2003

Introduction

One of the promises of .NET is that the language used is secondary to the framework. The classes in the CodeDom namespace really drive this point home. Using CodeDom, we build a tree, or graph, populated with objects from the System.CodeDom namespace, and after the tree is populated, we use the CodeProvider object provided by every .NET language to convert the tree into code in that language. This makes switching languages as simple as switching the CodeProvider used at the end.

Imagine some of the possibilities:

  • Query the metadata about your stored procedures to build a class to handle parameter binding.
  • Query the manifest of an assembly to build a class to unit-test every function.
  • Build standard boilerplate code for templates in every language your group uses.
  • Write sample code once, and allow the reader to view it in any language they choose.
  • Define your own template grammar and parse it to produce code in any language.
  • Generate code in a language that you are not familiar with, but are trying to learn, to compare to a language that you already know.

Related Reading

.NET Framework Essentials
By Thuan L. Thai, Hoang Lam

Background

The System.CodeDom namespace includes objects for representing, in a language-independent fashion, most language structures. Each language-specific CodeProvider has the responsibility of dealing with that language's subtle nuances. For example, the CodeConditionStatement includes a collection of TrueStatements, a collection of FalseStatements, and a Condition attribute, but does not worry about whether an "end if" or curly braces are needed. The CodeProviders work this out. This layer of abstraction allows us to structure code to be generated and then output it any .NET language without getting bogged down in the details of the language being generated. This abstraction also makes it easier to structure code programmatically. For example, we can add parameters to the Parameters collection of the method being generated as we discover that we need them, without interfering with the flow of the code already generated.

Basics

Most of the objects we will be using are found in the System.CodeDom namespace. The additional objects will be located in language-specific namespaces such as the Microsoft.CSharp namespace, the Microsoft.VisualBasic namespace, and the Microsoft.JScript and Microsoft.VJSharp namespaces. Each of the language-specific namespaces includes the respective CodeProviders. Finally, the System.CodeDom.Complier namespace will define the interface ICodeGenerator, which will be used to output the generated code to a TextWriter object.

If all we wanted to produce was code snippets for an add-in or macro, we would use the CodeGenerator to generate code from a Statement, Expression, Type, etc. If, on the other hand, we intended to generate an entire file, we would start with a CodeNameSpace object. In this example, we will start with a namespace and demonstrate how to add imports, declare a class, declare a method, declare a variable, implement a loop structure, and index an array. In the end, we will combine these various samples to produce the most beloved of all programs.

Initialize a Namespace

We can use a function similar to this to initialize our namespace.


private CodeNameSpace InitializeNameSpace(string Name) 
{
  // Initialize the CodeNameSpace variable specifying the name of
  // the namespace
  CodeNameSpace CurrentNameSpace = new CodeNamespace (Name);

  // Add the specified Name spaces to the collection of namespaces
  // to import.   The CodeProviders will handle figuring out how
  // name spaces are imported in their respective languages.
  CurrentNameSpace.Imports.Add (new CodeNamespaceImport("System"));
  CurrentNameSpace.Imports.Add (new CodeNamespaceImport("System.Text"));

  return CurrentNameSpace;
}

This code will define a new namespace and import the System and System.Text namespaces.

Create a Class

We can use a function similar to this to declare a new class:


private CodeTypeDeclaration CreateClass (string Name)
{
  // Create a new CodeTypeDeclaration object specifying the name of
  // the class to be created.   
  CodeTypeDeclaration ctd = new CodeTypeDeclaration (Name);

  // Specify that this CodeType is a class as opossed to an enum or a
  // struct
  ctd.IsClass = true;

  // Specify that this class is public
  ctd.Attributes = MemberAttributes.Public;

  // Return our freshly created class
  return ctd; 
}

This function will create a new class with the specified name ready to be populated with methods, properties, events, etc.

Create a Method

We can use a function similar to this to declare a new method:


private CodeEntryPointMethod CreateMethod()
{
  // Declare a new CodeEntryPointMethod
  CodeEntryPointMethod method = new CodeEntryPointMethod();

  // Specify that this method will be both static and public
  method.Attributes = MemberAttributes.Public |
  MemberAttributes.Static;
  
  // Return the freshly created method
  return method;
}

For this example, we created a CodeEntryPointMethod. This object is similar to the CodeMemberMethod object, except that the CodeProviders will ensure that the EntryPoint object will be called as the entry in the class, such as Sub Main or void main, etc. For the CodeEntryPointMethod, a name of Main is assumed; for CodeMemberMethod, you must specify the name.

Declare a Variable

We can use a function similar to this to declare a variable.


private CodeVariableDeclarationStatement 
            DeclareVariables(System.Type DataType,
                             string Name)
{
  // Get a CodeTypeReference for the Type 
  // of the variable we are about to
  // create.   This will allow us not to 
  // have to get bogged down in the
  // language specific details of specifying 
  // data types.
  CodeTypeReference tr = new CodeTypeReference (DataType );

  // The CodeVariableDeclarationStatement 
  // will allow us to not have to
  // worry about such details as whether 
  // the Data Type or the variable name
  // comes first or whether or not a key 
  // word such  as Dim is required.
  CodeVariableDeclarationStatement Declaration = 
             new CodeVariableDeclarationStatement(tr, Name);
  
  // The CodeObjectCreateExpression handles 
  // all of the details for calling
  // constructors.   In most cases, this 
  // will be new, but sometimes it is New.
  // At any rate, we don't want to  have to 
  // worry about such details.
  CodeObjectCreateExpression  newStatement = new
  CodeObjectCreateExpression ();
  
  // Here we specify the object whose 
  // constructor we want to invoke.
  newStatement.CreateType = tr;
  
  // Here we specify that variable will be 
  // initialized by calling its constructor.
  Declaration.InitExpression = newStatement;
  return Declaration;
}

The individual .NET languages can have their own names for the data types that all map to common .NET data types. For example, in C# the data type would be int. In VB.NET, the same data type would be Integer. The common .NET type is System.Int32. The CodeTypeReference object goes directly to the common .NET data type, and then the language specific Code providers can use the more common language-specific name.

Initializing an Array

We can use a function similar to this to initialize an array.


private void InitializeArray (string Name, 
                              params char[] Characters )
{
  // Get a TypeReference for the Character 
  // array that was passed in
  // so that we can duplicate this data 
  // type in our generated code.
  CodeTypeReference tr = new CodeTypeReference (Characters.GetType());
  
  // Declare an array that matches our local array
  CodeVariableDeclarationStatement Declaration =
    new CodeVariableDeclarationStatement (tr, Name);
  
  // The CodePrimitiveExpression object is used to 
  // represent "primitive" or value data types such 
  // as char, int, double, etc.    We will use
  // an array of these primitive expressions to 
  // initialize the array we are declaring.
  CodePrimitiveExpression[] cpe = new 
    CodePrimitiveExpression[Characters.Length];
  
  // Loop through our local array of characters, 
  // creating the objects
  // for our array of CodePrimitiveExpressions
  for (int i = 0; i < Name.Length ; i++)
  {
    // Each CodePrimitiveExpression will have a language
    // independant representation of a character
    cpe[i] = new CodePrimitiveExpression (Characters[i]);
  }

  // The CodeArrayCreateExpression will handle calling 
  // the default constructor for the data type in the 
  // array.   Because we are also passing in the array of
  // CodePrimitiveExpressions, we won't need to specify 
  // the size of the array, and
  // every value in the array will have its initial value.
  CodeArrayCreateExpression array = new 
    CodeArrayCreateExpression(tr, cpe);
  
  // Specify that this CodeArrayCreateExpression will 
  // initialize our array variable declartion
  Declaration.InitExpression = array;
  
  return Declaration;
}

Pages: 1, 2, 3

Next Pagearrow