Coding with the CodeDom

In contrast to Reflection.Emit, the CodeDom model is not specifically based on the .NET reflection architecture. Instead, because CodeDom is really aimed at generating source code, the class hierarchy is based on a document model that contains the kind of item usually found in source code: statements, expressions, type declarations, etc. Indeed, the underlying philosophy is very similar to the web page document model used in web page scripting languages.

Although this chapter focuses on the practical, coding side of dynamic code generation, I can't help feeling that perhaps one of the most exciting aspects of CodeDom may in the long term be the way that it can be viewed as representing a meta-language, in terms of which other programming languages can be defined, in much the same way that SGML can be used to define XML or HTML.

Creating a DOM

The first stage in generating a program with the CodeDom classes is to create the document object model that represents the code to be emitted.

Although the name CodeDom (or DOM) suggests a kind of document, it's important to understand that the CodeDom representation of a file does not exist as a file, but rather as a linked set of instances of the CodeDom classes. Strictly speaking, these classes are generally serializable, so if you really need a file representation of a CodeDom, you can get a suitable file by serializing the classes; the file would be quite large and not particularly human-readable, however. There are too many CodeDom classes to cover more than a small fraction of them in this chapter, but the following diagram shows how some of the more important classes fit into the structure. The diagram should also give you an idea of the basic principles behind the CodeDom architecture:

click to expand

Each box in the diagram indicates the type of item represented, followed by the CodeDom class used to represent this item. For example, the CodeCompileUnit class represents a complete program (something that can be compiled into an assembly as a unit). The arrows should be interpreted in a "contains reference to" sense - for example, the CodeCompileUnit implements a Namespaces property, which contains a collection of CodeNamespace objects (in a CodeNamespaceCollection instance). For simplicity in the diagram I've not drawn in the collection classes. In general, whenever multiple instances of an item are possible, you can surmise that there's an intermediate collection class lurking.

Although it's not shown in detail in the diagram, there is a further, fairly complex hierarchy below the statement level, depending on the nature of the statement. For example, a statement that calls a method is represent by a CodeMethodInvokeExpression instance (this class is derived from CodeStatement) - CodeMethodInvokeExpression contains further nested classes that provide information about the method to be invoked, the return type, and the parameters to be passed to it.

The underlying principle of the DOM is relatively simple. It is just a big tree structure, and as you move down the tree, you get closer and closer to the basic items that form a piece of code: the variable declarations and sub-expressions within a statement. However, the code needed to implement a DOM is quite complex because there are so many different types of item that can form part of the tree - which means that the System.CodeDom namespace contains a huge number of classes.

Although I say that the CodeDom has a tree structure, there is one complication to be aware of: the items in the "tree" are references and there's nothing wrong with multiple references referring to the same object. For example, suppose the statement x = x*2; is to occur several times in the code. This statement would be represented in the DOM by an instance of the class CodeAssignStatement (also derived from CodeStatement), but it would be wasteful to have a separate instance of this class for each statement - rather we will most likely have one such instance, with a reference to this instance at each point in the DOM where an x = x*2; statement occurs.

You can create the root of a CodeDom like this:

 CodeCompileUnit unit = new CodeCompileUnit(); 

The CodeCompileUnit.Namespaces property contains any namespaces you define:

 CodeNamespace ns = new CodeNamespace("Wrox.AdvDotNet.CodeDomSample"); unit.Namespaces.Add(ns); 

The CodeNamespace.Types property contains a collection of objects representing the types you define:

 CodeTypeDeclaration myClass = new CodeTypeDeclaration(); ns.Types.Add(theClass); 

And so on down the tree. You'll need, for example, to add members to the class declared in this code snippet. We'll present a couple of examples shortly that illustrate how to do this.

DOM to Source Code

Once we have a CodeCompileUnit object that contains all the details of the program to be generated, the next stage is to convert this "document" into the actual source code file. For this we need the base classes and interfaces defined in System.CodeDom.Compiler, and the implementations of these classes defined in various language-specific namespaces.

The programming model is based on the concept of a CodeDom provider. A CodeDom provider is a class that provides the entry point into the code generation facilities. Providers must derive from the abstract class, System.CodeDom.Compiler.CodeDomProvider. The providers supplied by Microsoft include Microsoft.CSharp.CSharpCodeProvider, Microsoft.VisualBasic.VBCodeProvider and Microsoft.JScript.JScriptCodeProvider.

The CodeDomProvider base class defines a CreateGenerator() method, which returns an ICodeGenerator interface reference that can be used to generate the source code from the CodeCompileUnit. Thus, for example, to generate C# source code you'd do this:

 CSharpCodeProvider cscp = new CSharpCodeProvider(); ICodeGenerator gen = cscp.CreateGenerator(); // unit is the CodeCompileUnit() reference // strm is a TextStream (or derived class) used to write out the source code // opts is a CodeGeneratorOptions instance that specifies code layout options gen.GenerateCodeFromCompileUnit(unit, strm, opts); 

We'll see these principles applied in an example soon, when we'll cover the CodeGeneratorOptions class.

This architecture allows for easy swapping between languages. If for example you want to generate VB source code instead, then the only line of this code you need to change is the statement that creates the code provider:

 VBProvider cscp = new VbProvider(); ICodeGenerator gen = cscp.CreateGenerator(); gen.GenerateCodeFromCompileUnit(unit, strm, opts); 

Source Code to IL

Dynamically compiling source code works on the same principles as generating source code - you start off with the relevant language code provider. Instead of calling CodeDomProvider.CreateGenerator(), however, you invoke another method, CodeDomProvider.CreateCompiler(). This method returns an ICodeCompiler interface reference, through which you can compile code:

 CSharpCodeProvider cscp = new CSharpCodeProvider(); ICodeCompiler compiler = cscp.CreateCompiler(); // compilerParams is a CompilerParameters object. // CompilerParameters contains fields that indicate compilation options compiler.CompileAssemblyFromFile(compilerParams, "MyCode.cs"); 

This code compiles the file MyCode.cs. As with the CodeGeneratorOptions, we'll examine use of the helper class CompilerParameters later in the chapter. And just as for generating source files, if you want to compile a file that is in a different language, you just create a different code provider but don't need to change any other code.

The ICodeCompiler interface offers other methods, among other things, to allow direct generation of an assembly from a CodeCompileUnit, without having to go through an intermediate source code file.

CodeDom Examples

We'll now present a couple of examples that illustrate the use of the CodeDom classes. Because CodeDom leads to very long code, we'll keep the examples simple. In fact, we won't get much further than generating simple console applications - but this will be enough to demonstrate the principles of CodeDom.

The CodeDomHelloWorld Example

This example uses the CodeDom classes to generate and then compile a C# application. The application is similar to a simple Hello World application, but we are going to take advantage of dynamic code generation to vary the generated code based on user preferences. Instead of just displaying Hello, World!, the example asks the user what string should be displayed and how many times this string should be displayed - and it then generates a custom C# program according to the user's preferences. The actual source code generates looks like this (assuming the user typed in the string Hello, World! and asked for a program that would display it 5 times):

 // <autogenerated> //     This code was generated by a tool //     Runtime Version: 1.0.3705.288 // //     Changes to this file may cause incorrect behavior and will be lost if //     the code is regenerated. // </autogenerated> namespace Wrox.AdvDotNet.CodeDomSample {    using System;    public class EntryPoint    {       public static void Main()       {          for (int i = 0; (i < 5); i = (i + 1))          {             System.Console.WriteLine("Hello, World!");          }          return;       }    } } 

The initial comments are automatically added by the C# code generator to every program. You'll notice that some of the constructs are not quite what you'd probably write if you were coding by hand - for example, the increment operation in the for loop has been emitted as i = i + 1, where most of us would more likely write ++i. Because the CodeDom is intended to be multilanguage compatible, it doesn't support certain constructs that are unique to certain languages - such as the C#/C++ increment operator - hence the more cumbersome code.

Now let's examine the code that for the example which generates the above file.

As we saw earlier, the process of getting to a compiled assembly through the CodeDom mechanism involves three steps:

  1. Create the CodeDom structure

  2. Generate the source code

  3. Compile the source code

In a real application, it's highly unlikely that all three steps would follow each other in the same program - if that were your intention, you'd get far better performance (and, provided you're familiar with IL, find the code easier to write) if you used the Reflection.Emit classes to generate the assembly directly. However, in order to illustrate all the processes, running through the above steps is exactly what our example will do.

Let's start off with the code that generates the language-neutral CodeDom representation of the generated program. That's implemented in a method that I've called GenerateProgram(). This method returns the CodeCompileUnit instance that represents the program:

 // generate CodeDOM for code that displays message nDisplays times. static CodeCompileUnit GenerateProgram(string message, int nDisplays) {    // Create Main method    CodeEntryPointMethod mainMethod = new CodeEntryPointMethod();    mainMethod.Name = "Main";    // generate this expression: Console    CodeTypeReferenceExpression consoleType = new       CodeTypeReferenceExpression();    consoleType.Type = new CodeTypeReference(typeof(Console));    // generate this statement:int i=0;    CodeVariableDeclarationStatement declareI =                                   new CodeVariableDeclarationStatement();    declareI.Name = "i";    declareI.InitExpression = new CodePrimitiveExpression(0);    declareI.Type = new CodeTypeReference(typeof(int));    // generate this expression: i;    CodeVariableReferenceExpression iVar = new                           CodeVariableReferenceExpression(declareI.Name);    // generate this statement: i=i+1;    CodeAssignStatement incrI = new CodeAssignStatement();    incrI.Left = iVar;    incrI.Right = new CodeBinaryOperatorExpression(iVar,              CodeBinaryOperatorType.Add, new CodePrimitiveExpression(1));    // generate this for loop: for (int i=0 ; i<nDisplays ; i++)    CodeIterationStatement forLoop = new CodeIterationStatement();    forLoop.InitStatement = declareI;    forLoop.TestExpression = new CodeBinaryOperatorExpression(iVar,                                   CodeBinaryOperatorType.LessThan,                                   new CodePrimitiveExpression(nDisplays));    forLoop.IncrementStatement = incrI;    // Set up the argument list to pass to Console.WriteLine()    CodeExpression[] writeLineArgs = new CodeExpression[1];    CodePrimitiveExpression arg0 = new CodePrimitiveExpression(message);    writeLineArgs[0] = arg0;    // generate this statement: Console.WriteLine(message)    CodeMethodReferenceExpression writeLineRef = new       CodeMethodReferenceExpression(consoleType, "WriteLine");    CodeMethodInvokeExpression writeLine = new       CodeMethodInvokeExpression(writeLineRef, writeLineArgs);    // insert Console.WriteLine() statement into for loop    forLoop.Statements.Add(writeLine);    // add the for loop to the Main() method    mainMethod.Statements.Add(forLoop);    // Add a return statement to the Main() method    CodeMethodReturnStatement ret = new CodeMethodReturnStatement();    mainMethod.Statements.Add(ret);    // Add Main() method to a class    CodeTypeDeclaration theClass = new CodeTypeDeclaration();    theClass.Members.Add(mainMethod);    theClass.Name = "EntryPoint";    // Add namespace and add class    CodeNamespace ns = new CodeNamespace("Wrox.AdvDotNet.CodeDomSample");    ns.Imports.Add(new CodeNamespaceImport("System"));    ns.Types.Add(theClass);    // Create whole program (code compile unit) CodeCompileUnit unit = new    CodeCompileUnit();    unit.Namespaces.Add(ns);    return unit; } 

There's quite a lot going on here, so we'll go through it in detail. We start off by instantiating the classes needed to represent a method, and give this method the name Main:

 CodeEntryPointMethod mainMethod = new CodeEntryPointMethod(); mainMethod.Name = "Main"; 

The CodeEntryPointMethod class is derived from CodeMemberMethod, and indicates a method that will form the entry point to an executable.

The next few lines of code are to do with constructing that Console.WriteLine() statement. CodeDom really breaks the source code up into its most basic elements - and the thing we deal with first in our code is sorting out a reference to the Console type. We don't need to actually define the type here - just somehow indicate that we are referencing an existing type - so the CodeTypeDeclaration class used to declare types is not appropriate. The class we need is CodeTypeReference.CodeTypeReference is little more than a wrapper for the System.Type class, but it contains a couple of CodeDom-specific properties related to getting information from an array. The CodeTypeReference object needs to be embedded into a CodeTypeReferenceExpression instance - this is the class that is used to represent types that are present in expressions in source code.

 // generate this expression: Console CodeTypeReferenceExpression consoleType = new CodeTypeReferenceExpression(); consoleType.Type = new CodeTypeReference(typeof(Console)); 

The next task is to generate the statement int i=0; which goes inside the for loop:

 CodeVariableDeclarationStatement declareI = new       CodeVariableDeclarationStatement(); declareI.Name = "i"; declareI.InitExpression = new CodePrimitiveExpression(0); declareI.Type = new CodeTypeReference(typeof(int)); 

It'll be no surprise to learn that statements that declare variables are represented by the CodeVariableDeclarationStatement class. The other new class here, CodePrimitiveExpression, represents any constant numeric or string expression, such as 23, 0 or "Hello, World!"

The code to be generated will refer to the variable i a few times - so to take account of this we'll cache a reference to this variable:

 CodeVariableReferenceExpression iVar = new    CodeVariableReferenceExpression(declareI.Name); 

Followed by the increment statement that will go in the loop: i = i + 1:

 CodeAssignStatement incrI = new CodeAssignStatement(); incrI.Left = iVar; incrI.Right = new CodeBinaryOperatorExpression(iVar,    CodeBinaryOperatorType.Add, new CodePrimitiveExpression(1)); 

The CodeBinaryOperatorExpression class represents any expression of the form x op y - in our case i+1 - the expression (i+1) forms the right hand side of a CodeAssignStatement. (I'm sure you can guess the role of the CodeAssignStatement class...)

We are now ready to put that for statement together, using the int i=0 and i=i+1 statements we've just generated, as well as another CodeBinaryOperatorExpression that will represent the i<nDisplays condition:

 CodeIterationStatement forLoop = new CodeIterationStatement(); forLoop.InitStatement = declareI; forLoop.TestExpression = new CodeBinaryOperatorExpression(iVar,    CodeBinaryOperatorType.LessThan, new CodePrimitiveExpression(nDisplays)); forLoop.IncrementStatement = incrI; 

The next thing we need to do is sort out the argument list that will need to be passed to Console.WriteLine(). The argument list should be represented by an array of CodeExpression references. CodeExpression is the base class for a number of classes that can represent different types of expressions in this context. We have already encountered two specific expression classes derived from CodeExpression:CodePrimitiveExpression and CodeBinaryOperatorExpression. In our case, Console.WriteLine() takes one string argument. As far as CodeDom is concerned, string counts as a primitive type, along with int, float, bool, etc., and so can be represented as a CodePrimitiveExpression instance.

 CodeExpression[] writeLineArgs = new CodeExpression[1]; CodePrimitiveExpression arg0 = new CodePrimitiveExpression("Hello, World!"); writeLineArgs[0] = arg0; 

Now we have all the units necessary to build up the Console.WriteLine() statement:

 CodeMethodReferenceExpression writeLineRef = new           CodeMethodReferenceExpression(consoleType, "WriteLine"); CodeMethodInvokeExpression writeLine = new           CodeMethodInvokeExpression(writeLineRef, writeLineArgs); 

In the above code, we start by defining a reference to the method we need to call - a CodeMethodReferenceExpression instance. CodeMethodReferenceExpression requires a reference to the object or type against which the method is to be called, and the name of the method. The first line of the above code gives us a CodeMethodReferenceExpression which identifies the Console.WriteLine() method. However, by itself that's not sufficient: CodeMethodReferenceExpression does not encapsulate any information about how the method is to be used - for example is it to be invoked, or passed as a parameter to a delegate. We want to invoke the method, so we pass the CodeMethodReferenceExpression to a CodeMethodInvokeExpression object, along with the parameter list. At this point we at last have an object that represents an expression, which can be used as a full statement. We thus insert this statement into the body of the for loop, and then insert the now completed for loop into the Main() method.

 forLoop.Statements.Add(writeLine); mainMethod.Statements.Add(forLoop); 

The remainder of the GenerateProgram() method is relatively easy to follow, so we won't go through it in as much detail. We add the return statement to the method; return statements are represented by the CodeMethodReturnStatement class. Then we instantiate a new CodeTypeDeclaration object and add the method to it. Finally, we declare a namespace, add the class to it, and add the namespace, along with a reference to the System namespace, to the newly created CodeCompileUnit. At this point you can probably see why I've not gone for a more complicated example!

Now we have a document tree, we can examine the code used to generate and compile a source code file that corresponds to this DOM:

 [STAThread] static void Main(string[] args) {   Console.WriteLine("What string do you want the custom program to " +                                                                "display?");   string message = Console.ReadLine();   Console.WriteLine("How many times do you want the program to display " +                                                           "this message?");   int nDisplays = int.Parse(Console.ReadLine());   CodeCompileUnit unit = GenerateProgram(message, nDisplays);   // Set up options for source code style   CodeGeneratorOptions opts = new CodeGeneratorOptions();   opts.BracingStyle = "C";   opts.IndentString = "\t";   // Create code generator and write code file   CSharpCodeProvider cscp = new CSharpCodeProvider();   ICodeGenerator gen = cscp.CreateGenerator();   StreamWriter sw = new StreamWriter("MyCode.cs");   gen.GenerateCodeFromCompileUnit(unit, sw, opts);   sw.Close();   CompilerParameters compilerParams = new CompilerParameters();   compilerParams.GenerateExecutable = true;   compilerParams.OutputAssembly = "MyCode.exe";   ICodeCompiler compiler = cscp.CreateCompiler();   compiler.CompileAssemblyFromFile(compilerParams, "MyCode.cs"); } 

In order to generate code, we first need a CodeGeneratorOptions object, which is used to specify options for the layout of code. We use this to set up two options. BracingStyle indicates how braces should be arranged. It takes a string, and the values of this string will depend on the language we are compiling to. For Microsoft languages, the options are C, which gives code that looks like this:

 public class EntryPoint {    public static void Main()    { 

and Block, which gives code like this:

 public class EntryPoint {    public static void Main() { 

When generating VB code, BracingStyle is ignored. In general, the fact that BracingStyle is a string allows other values to be assigned to it, which may be recognized by different language source code generators.

We also have assigned the IndentString property to a tab character. This is the string that will be used to supply an indent to code in blocks. If we don't assign it explicitly, it defaults to four spaces. There are also two properties of type bool, which we aren't explicitly assigning to in this example: BlankLinesBetweenMembers controls whether blank lines should be inserted between member and type declarations, while InsertElseOnClosing controls whether if statements should always be terminated with an else, and try blocks followed by a finally block, even if there are no statements to be inserted into such blocks.

Having defined the CodeGeneratorOptions, we can instantiate the code generator which implements ICodeGenerator, and call its GenerateCodeFromCompileUnit() method to generate the code, passing it a StreamWriter that the code will be output to.

Having done that, we can compile the code. To do this, we use the provider's CreateCompiler() method to get the interface pointer to a code compiler, and call the CompileAssemblyFromFile() method to obtain the assembly. We also pass a CompilerParameters object to this method, which we initialize to indicate we want to create an executable file.

CodeDom Gotchas

One point to watch about the CodeDom is that it's not particularly good at noticing syntax errors in your program. Although the structure of the CodeDom tree automatically enforces a reasonable structure on the generated code, it's very easy to allow absurd syntax errors to creep in undetected. A couple of examples should serve to indicate the kind of problems you need to look out for.

The CodeGeneratorOptions.IndentString property can be used with great effect to produce ridiculous source code. Suppose, for example, that we introduce the following bug. Instead of writing opts.IndentString = "\t";, we incorrectly use "t" instead of "\t", thus defining an indent string containing the letter t instead of the tab character. The C# CodeDom generator will (at least at the time of writing) quite happily accept this, impervious to the fact that only whitespace characters should be used in C# to indent strings. When you run our example with this bug, you get a completely uncompilable file emitted, part of which looks like the following:

 ttpublic static void Main() tt{ tttSystem.Console.WriteLine("Hello, World!"); tttreturn; tt} t} 

As another example, in this example I was very careful to wrap the CodeMethodReferenceExpression object that indicated the Console.WriteLine() statement in a CodeMethodInvokeExpression. In fact, the code would compile correctly if I omitted this step.

 // WRONG! CodeMethodReferenceExpression writeLineRef = new      CodeMethodReferenceExpression(consoleType, "WriteLine"); mainMethod.Statements.Add(writeLineRef); 

This works because the CodeMemberMethod.Statements property is of type CodeStatementCollection - and the CodeStatementCollection.Add() method will take either a CodeStatement or a CodeExpression reference. Both CodeMethodReferenceExpression and CodeMethodInvokeExpression are derived from CodeExpression. This flexibility is needed because of the theoretical possibility that any code expression might be valid as a statement in some languages, as is the case in C++ or Perl, for example. However, in C#, the above code generates the following:

 public static void Main() {    System.Console.WriteLine;    return; } 

The emitted file contains exactly what we've asked for - a method reference. But clearly in this context, the generated source code is meaningless and will not compile.

The lesson from this is that when using CodeDom you will need to be very careful and precise in your use of the CodeDom classes, to make sure that you request exactly the source code that you need.

A related issue is that, although CodeDom is theoretically language-independent, in practice the idiosyncracies and lack of support for some constructs in some languages does get in the way, so that it is possible to create a CodeDom that generates correct code in one language but not another.

Generating Files in Multiple Languages

The last example has shown how to use CodeDom, but has probably not left you with a positive feeling about the capabilities and ease of use of the CodeDom classes. In this section we'll develop the example into a new example that will hopefully rectify that to some extent. The CodeDomMultiLanguage example will show how easy it is to use CodeDom to swap between languages. The example is the same as the previous example, but with a few changes to generate source files in not only C# but also VB and JScript.NET.

Since the underlying code is the same, the GenerateProgram() method is unchanged. We simply need to change the way that the generated CodeCompileUnit object is processed. Hence we make the following changes to the Main() method:

 static void Main(string[] args) {    Console.WriteLine("What string do you want the custom program to " +                                                              "display?");    string message = Console.ReadLine();    Console.WriteLine("How many times do you want the program to display " +                                                         "this message?");    int nDisplays = int.Parse(Console.ReadLine());    CodeCompileUnit unit = GenerateProgram(message, nDisplays);    // Set up options for source code style    CodeGeneratorOptions opts = new CodeGeneratorOptions();    opts.BracingStyle = "C";    opts.IndentString = "\t";    // Create code generators and write code files    CodeDomProvider[] providers = new CodeDomProvider[3];    providers[0] = new CSharpCodeProvider();    providers[1] = new VBCodeProvider();    providers[2] = new JScriptCodeProvider();    string[] fileNames = { "MyCodeCS.cs", "MyCodeVB.vb", "MyCodeJS.js" };    for(int i=0 ; i< providers.Length; i++) {       ICodeGenerator gen = providers[i].CreateGenerator();       StreamWriter sw = new StreamWriter(fileNames[i]);       gen.GenerateCodeFromCompileUnit(unit, sw, opts);       sw.Close(); }                   .    string[] assemblyFileNames = { "MyCodeCS.exe", "MyCodeVB.exe",                                   "MyCodeJS.exe" };    CompilerParameters compilerParams = new CompilerParameters();    compilerParams.GenerateExecutable = true;    for (int i=0; i<providers.Length; i++)    {       ICodeCompiler compiler = providers[i].CreateCompiler();       compilerParams.OutputAssembly = assemblyFileNames[i];       compiler.CompileAssemblyFromFile(compilerParams, fileNames[i]);    } } 

Instead of creating one single provider, we create an array of providers, one for each language. Then we iterate through the array - for each one, using the CodeDomProvider.CreateGenerator() method to instantiate the appropriate code generator. Having done that we repeat the technique to instantiate the required compilers that will generate the assemblies.

For the sake of completeness, the source code files emitted for VB and JScript.NET are as follows:

VB:

 Option Strict Off Option Explicit On Imports System Namespace Wrox.AdvDotNet.CodeDomSample    Public Class EntryPoint       Public Shared Sub Main()          Dim i As Integer = 0          Do While (i < 5)             System.Console.WriteLine("Hello, World!")             i = (i + 1)          Loop          Return       End Sub    End Class End Namespace 

JScript.NET:

 //@cc_on //@set @debug(off) import System; package Wrox.AdvDotNet.CodeDomSample {    public class EntryPoint    {       public static function Main()       {          for (var i : int = 0;          ; (i,< 5); i = (i + 1))          {             System.Console.WriteLine("Hello, World!");          }          return;       }    } } Wrox.AdvDotNet.CodeDomSample.EntryPoint.Main(); 

To save space, in both of these listings I've omitted the initial comments that warn that these are auto-generated files. The VB output is especially informative for the way it shows how the code provider has constructed the loop: it's given us a Do While loop instead of a For loop - you or I would probably have written For I = 1 To 5 ... Next here. The reason the CodeDom generator hasn't done so is of course that the Do While loop is VB's nearest equivalent in terms of richness to C#'s for loop. Emitting a For loop would require the CodeDom provider to be able to analyze the loop and determine that it is controlled by a simple increment that could be more conveniently represented by a VB For loop. The VB code provider is evidently not quite that sophisticated and takes the easy way out - using a loop construct that will always work whatever the loop condition and increment statements.



Advanced  .NET Programming
Advanced .NET Programming
ISBN: 1861006292
EAN: 2147483647
Year: 2002
Pages: 124

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net