Generating .Net Source Code
(by Joe Williams for Blayd Software)
Note: this article relates to the .Net Framework version 2.0, some
of the classes and class members referenced in this article were not available in
earlier framework versions.
See Also
.Net Framework source code generation services and products provided by Blayd Software.
CodeDOM Helper Library
The CodeDOM Helper Library provides an abstraction layer over the types in the System.CodeDom
namespace. Developers can use the CodeDOM Helper library types to simplify the process
of creating a Code Document Object Model and can easily generate source code from
the model in any, supported, .Net Framework language.
CodeDOM Helper Class Library Component
Online Source Code Generation
We are currently offering a selection of generic and non-generic collection base
classes from which you can generate your own, closed constructed, derived collections.
There is also a selection of, ready to run, open constructed, generic comparers
to choose from.
Online .Net Framework Source Code Generator
Introduction
The .Net Framework enables different, supported, languages to be compiled into a
common, intermediate language (MSIL) that can then be either pre-compiled or just-in-time
compiled to machine code. The framework's Common Type System (CTS) enables objects
written in one language to interact with and be used by objects written in another
language and the Common Language Runtime (CLR) provides a managed environment in
which objects are executed.
We appreciate that the previous paragraph is a very simplistic overview of the .Net
Framework, however, if you have been developing with the framework for any time
you will or should be familiar with the basic principals. If you are new to the
framework there are plenty of resources available, books, magazines and online that
explain the fundamentals of the .Net Framework in far more detail than we can in
this article.
The .Net Framework includes a sub-system, called the Code Document Object Model
(CodeDOM), that enables the language-independent modelling of source code. The model
allows developers to emit source code, at runtime, in any supported .Net language,
from a single CodeDOM. The System.CodeDom namespace contains the types used to construct
a Code Document Object Model or source code object graph and the System.CodeDom.Compiler
namespace contains the types used to generate and compile the source code in a,
supported, .Net Framework language. We will be concentrating on constructing a source
code object graph and generating source code in this article rather than compiling
and emitting assemblies, however, once you have generated the source code, compiling
it to an assembly is fairly straight forward.
The ability to generate source code at runtime can be useful in many situations,
one of the most obvious being the development of productivity tools to automate
the production of boilerplate code. Custom control and component designers may also
have to generate source in order to serialize their state to allow the control or
component to be created and initialized at runtime. Tools such as data access layer
and object relational mapping generators are plentiful and presumably, therefore,
popular. UML modelling applications and Class designers are also plentiful and both
of these types of application, typically, have extensive code generation functionality.
The recurring theme in virtually all applications of source code generation seems
to be productivity, however accuracy and consistency also have important parts to
play. Automating the production of what is essentially boilerplate source code or
generating source code from another source that is easier to create or does not
require programming skills to create, such as UML or XML created in a user friendly
UI, for example, can make the effort required to master the Code Document Object
Model worthwhile.
The sample code used in this article is CSharp as there is very little difference
between CSharp and Visual Basic in most cases. Where the source code generated from
the sample code is shown we will show both CSharp and Visual Basic, in most cases,
as this serves to emphasise how the same generation code can produce different results
in different code providers. You can use any .Net language to write a code generator
and you can output source code in any .Net language, for example, a code generator
written in CSharp can output CSharp and Visual Basic source code and a code generator
written in Visual Basic can output Visual Basic and CSharp source code.
Code Providers
The CodeDOM provides a language-independent representation of source code and language-specific
code providers are used to translate the source code object graph into language-specific
source code. The System.CodeDom.Compiler.CodeDomProvider abstract base class is
extended by all of the framework's built-in language-specific code providers, for
example, both the Microsoft.CSharp.CSharpCodeProvider class and the Microsoft.VisualBasic.VBCodeProvider
class extend CodeDomProvider. Therefore, to generate source code from your source
code object graph, you first have to create the relevant code provider instance.
There are various ways to create a code provider instance, you can, for example,
create the relevant provider instance directly if you are only generating source
code in one language. However, if you are supporting more than one .Net Framework
language you will, typically, have to create a provider instance dynamically using
the language name as specified or selected by the user.
You can use the static CodeDomProvider.GetAllCompilerInfo method to enumerate the
code providers installed on a machine as follows:
Enumerate code providers:
CompilerInfo[] providers = CodeDomProvider.GetAllCompilerInfo();
foreach (CompilerInfo info in providers)
{
Console.WriteLine("Provider Type: {0}", info.CodeDomProviderType.FullName);
Console.WriteLine("Defined Languages:");
string[] languages = info.GetLanguages();
foreach (string language in languages)
{
Console.WriteLine(" {0}", language);
}
Console.WriteLine();
}
The code above produces the following output on one of
our test machines:
Provider Type: Microsoft.CSharp.CSharpCodeProvider
Defined Languages:
c#
cs
csharp
Provider Type: Microsoft.VisualBasic.VBCodeProvider
Defined Languages:
vb
vbs
visualbasic
vbscript
Provider Type: Microsoft.JScript.JScriptCodeProvider
Defined Languages:
js
jscript
javascript
Provider Type: Microsoft.VJSharp.VJSharpCodeProvider
Defined Languages:
vj#
vjs
vjsharp
Provider Type: Microsoft.VisualC.CppCodeProvider
Defined Languages:
c++
mc
cpp
If you run the enumeration sample code on your own machine your output may vary
from the example above as you may have fewer or more code providers installed and
configured on your machine. Developers and compiler vendors can install and configure
their own language specific providers by specifying the provider settings in the
system.codedom section of the machine.config file.
You can use the static CodeDomProvider.IsDefinedLanguage method to check if there
is a provider for a specific language. Most providers define more than one language
name, as demonstrated in the sample output above, for example, you can create a
CSharp provider instance using "c#", "cs" or "csharp".
Test for language support:
Console.WriteLine("C# is defined: {0}",
CodeDomProvider.IsDefinedLanguage("C#"));
Console.WriteLine("CS is defined: {0}",
CodeDomProvider.IsDefinedLanguage("CS"));
Console.WriteLine("CSharp is defined: {0}",
CodeDomProvider.IsDefinedLanguage("CSharp"));
The code above produces the following output:
C# is defined: True
CS is defined: True
CSharp is defined: True
As demonstrated above the language name comparison is case insensitive, so you can,
for example, specify either "cs" or "CS" as the language name.
When you have a language name and you have confirmed that the name is defined by
an installed code provider you can use the language name in a call to the static
CodeDomProvider.CreateProvider method to create an instance of the relevant provider.
Create a code provider instance:
CodeDomProvider provider = CodeDomProvider.CreateProvider("CS");
The CodeDomProvider.CreateProvider method should only be used when you do not know
until runtime which language you will be generating source code in. If you are only
ever going to be generating source code in one specific language or if you are only
going to be generating source code in one or two known languages then it is better
to create the relevant code provider explicitly, for example, if you are only generating
CSharp source code then explicitly create an instance of the CSharpCodeProvider
class. The reason for this is due to the way that the CodeDomProvider.CreateProvider
method works, if there is more than one code provider available for the specified
language name, the method returns a provider instance for the last matching configuration
element. In most cases this won't matter, however, if you want to be sure that you
are using a particular provider for a specific language then create the provider
instance yourself.
The reason that we are discussing code providers before we have discussed how to
create a source code object graph is to highlight one or two gotchas that you might
run into when using a code provider to generate your source code. As mentioned previously
the CodeDOM is a language-independent representation of the source code and a language
specific code provider is used to actually generate the source code from the CodeDOM.
When building a source code object graph you will be specifying the Type of code
elements repeatedly, for example, the type of a field, the type of a parameter,
the type of a property, etc. If you are intending to generate source code in more
than one language and to some extent even if you are only intending to generate
in one language, care needs to be taken when specifying types. The following examples
show various ways in which a field can be created in the source code object graph.
Creating a set of example private fields:
new CodeMemberField("int", "_testField1");
new CodeMemberField("System.Int32", "_testField2");
new CodeMemberField(typeof(int), "_testField3");
new CodeMemberField(new CodeTypeReference("int"), "_testField4");
new CodeMemberField(new CodeTypeReference("System.Int32"), "_testField5");
new CodeMemberField(new CodeTypeReference(typeof(int)), "_testField6");
The generated CSharp source code including the above example fields:
public class TestClass
{
private @int _testField1;
private int _testField2;
private int _testField3;
private @int _testField4;
private int _testField5;
private int _testField6;
}
The generated Visual Basic source code including the above example fields:
Public Class TestClass
Private _testField1 As int
Private _testField2 As Integer
Private _testField3 As Integer
Private _testField4 As int
Private _testField5 As Integer
Private _testField6 As Integer
End Class
Don't worry too much about where the class came from or how the fields were added
to it for now, we will be discussing that in detail later in the article. The point
to note from this example is how easy it is to get it wrong, as neither the CSharp
or Visual Basic generated code above will compile (assuming that the relevant project
does not contain a class named "int").
When specifying the type of a code element we strongly recommend that where possible
you use a Type e.g. typeof(int) (GetType(Integer) in
Visual Basic). If you have to use a string to specify the type of a code element and you may
have to in some circumstances we recommend that you use a namespace qualified type name
e.g. "System.Int32", "System.String", "System.DateTime", etc.
Using this technique enables the relevant code provider to translate the code element correctly
and output valid source code in the relevant language.
The types in the System.CodeDom namespace are used to build a language-independent
CodeDOM or source code object graph and language-specific code providers are used
to translate the source code object graph into source code for their respective
language. The System.CodeDom types provide good support for modelling types e.g.
class, interface, struct, etc. and good support for modelling type members and member
signatures e.g. fields, properties, methods, etc. but the support for modelling
the code structures used, for example, in the body of a method is pretty basic.
Therefore, it is very easy to generate the source code for a class template or for
an interface, however, creating the code body for anything other than a very basic
method can be challenging. When you think about the multitude of statements, expression
and code structures supported by a modern language and compiler and then consider
whether a particular feature is only supported by some of the languages or is supported
by all of the languages, it soon becomes obvious that including a type or types
to model every possible feature is asking a lot.
The System.CodeDom namespace contains several code snippet types that can be used
to include literal source code in the CodeDOM or source code object model. These
types store a literal source code segment in its original form as a string which
is then output, as is, when source code is generated from the source code object
model. Using one of these types it is possible to output any source code, therefore,
you can generate complex code structures or method bodies with ease. However, you
guessed it, there is a catch, code snippets are language specific, they will not
be translated by a code provider, therefore, they have to be in the target language.
If you are developing a code generator that only supports one .Net language, literal
code snippets are not a problem, if your generator supports more than one language
then literal code snippets can be a real pain. Including literal code snippets for
every .Net language is not, typically, a viable option because as soon as a new
code provider arrives on the market, your code generator is compromised and no longer
works with all .Net languages. Ideally, when using literal code snippets you should
have a fallback position, for example, you may create language specific snippets
for CSharp and Visual Basic, the most commonly used .Net languages and also include
code elements created using the types available in System.CodeDom for any language
other than CSharp or Visual Basic.
When you are developing a code generator you have to consider how you are going
to manage user expectations. Typically, code generators are used by developers and
a developer may well expect the generated source code to use the techniques that
they would use themselves. For example, when iterating through a collection a developer
would, probably, expect the generated code to use a foreach (For Each in Visual
Basic) loop, however, you can't generate a foreach loop without resorting to a literal
code snippet. In our experience, when developing a code generator compromises have
to be made, in some places the generated source code will not be as "pretty" as
it could be and occasionally the generated source code will not be as efficient
as it could be but in all cases the generated source must compile and work as expected
and produce accurate results.
If you are developing a code generator that supports multiple .Net languages we
strongly recommend that you perform some validation after creating a code provider
instance but before generating the source code. The CodeDomProvider base class defines
several methods, that should be overridden by a concrete code provider class, that
can be used to check for language feature support and identifier validation and
creation.
- CreateEscapedIdentifier
-
The
CreateEscapedIdentifier method checks if the passed in identifier string is
a reserved or key word in the relevant language. If it is, the method returns the
language-specific escaped version of the passed in value. For example, passing in
the value "class" to the CSharp code provider would result in the value "@class"
being returned whilst the Visual Basic code provider would return "[class]". If
the passed in value is not a reserved or key word it is returned unaltered.
- CreateValidIdentifier
-
The
CreateValidIdentifier method checks if the passed in identifier string is a
reserved or key word in the relevant language. If it is, the method attempts to
alter the identifier to resolve the conflict. The method usually adds an underscore
character ("_") to the start of the identifier, for example, passing in the value
"class" to the CSharp or the Visual Basic code provider would, typically, result
in the value "_class" being returned. If the passed in value is not a reserved or
key word it is returned unaltered.
- IsValidIdentifier
-
The
IsValidIdentifier method checks if the passed in identifier string is a reserved
or key word in the relevant language. If it is, the method returns false, if it
is not, the method returns true.
- Supports
-
The
Supports method checks if the relevant language supports the features passed
in. The method accepts one or more of the System.CodeDom.Compiler.GeneratorSupport
enumerated values. The GeneratorSupport enumeration is a bit field i.e. it is decorated
with the FlagsAttribute, therefore the passed in value can specify one or more language
features. The method returns true if all of the specified language features are
supported and false if, at least, one specified feature is not supported.
Code Document Object Model
The Code Document Object Model (CodeDOM) is implemented as a hierarchy with a single
System.CodeDom.CodeCompileUnit class or a class derived from CodeCompileUnit at
its root.
Some of the main classes that we will be discussing in this article fit into the
CodeDOM as depicted in Figure 1 below:
The CodeCompileUnit class has a Namespaces property that references a collection
of CodeNamespace instances, therefore a CodeCompileUnit can contain one or more
namespaces.
The CodeNamespace class has a Types property that references a
collection of CodeTypeDeclaration instances, therefore a CodeNamespace
can contain one or more types. The CodeTypeDeclaration class represents a type
i.e. class, enum, interface or stuct.
The CodeTypeDeclaration class has a Members property that references a collection
of CodeTypeMember instances, therefore a CodeTypeDeclaration can contain one or
more members. The CodeTypeMember class is a base class from which more specific
types of member are derived e.g. CodeTypeMemberField, CodeTypeMemberProperty,
CodeTypeMemberMethod, etc.
Specialized CodeTypeMember derived classes that can have a code body
e.g. CodeTypeMemberMethod, typically, have a Statements property
that references a collection of CodeStatement instances.
The CodeTypeMemberProperty class has a GetStatements property for the
getter statements and a SetStatements property for the setter statements.
The CodeDOM is more complex than we have shown here, we have concentrated on the
classes that you will, typically, use most when generating source code.
Generating a Class
To generate a type we have to create a CodeCompileUnit instance, add
a CodeNamespace instance to it and then create a CodeTypeDeclaration
instance and add that to the namespace.
Sample code for generating a class:
CodeCompileUnit graph = new CodeCompileUnit();
CodeNamespace samples = new CodeNamespace("Samples");
samples.Imports.Add(new CodeNamespaceImport("System"));
graph.Namespaces.Add(samples);
CodeTypeDeclaration sampleClass = new CodeTypeDeclaration("Customer");
samples.Types.Add(sampleClass);
The generated CSharp source code for the example Customer class:
namespace Samples {
using System;
public class Customer {
}
}
The generated Visual Basic source code for the example Customer class:
Imports System
Namespace Samples
Public Class Customer
End Class
End Namespace
The interesting and possibly frustrating thing about both the CSharp and Visual
Basic generated source code is that neither is really what you might expect! A CSharp
developer will, probably be more used to the using statement being above
the namespace statement and a Visual Basic developer, probably, did not
want a sub-namespace named "Samples". However, both the CSharp and Visual Basic
generated source code is valid and will compile.
When using Visual Studio, adding a Namespace statement to a Visual Basic class file,
typically, creates a sub-namespace as the main or root namespace is usually specified
at the project level. Therefore, typically, when generating the source code for
a Visual Basic class we would not want or need to include a Namespace statement,
although we would need the option of including it when necessary. There is a very
simple tweak that we can make to the generation code to accommodate an optional
namespace statement, we must have at least one namespace in the source code object
graph but it can have an empty name and if it does have an empty name it won't appear
in the generated source code.
Sample code for generating a class without a namespace
statement:
CodeCompileUnit graph = new CodeCompileUnit();
CodeNamespace samples = new CodeNamespace(string.Empty);
samples.Imports.Add(new CodeNamespaceImport("System"));
graph.Namespaces.Add(samples);
CodeTypeDeclaration sampleClass = new CodeTypeDeclaration("Customer");
samples.Types.Add(sampleClass);
The generated Visual Basic source code for the sample code above:
Imports System
Public Class Customer
End Class
Although this a language-specific tweak, as we would not want to generate a CSharp
class without a namespace statement, it is fairly easy to implement if the code
generator is providing the user with a choice of generation languages. When the
user chooses to generate Visual Basic source code the component that is generating
the source code can be passed Empty for the namespace value.
We can use a variation on the empty namespace tweak to get the CSharp generated
class declaration to look like we would expect. However, this is language-specific
tweak that will require a conditional statement in the code generator e.g. if language
is CSharp apply the tweak, else don't apply the tweak.
Sample code for generating a more familiar CSharp namespace
and class declaration:
CodeCompileUnit graph = new CodeCompileUnit();
CodeNamespace dummy = new CodeNamespace(string.Empty);
dummy.Imports.Add(new CodeNamespaceImport("System"));
graph.Namespaces.Add(dummy);
CodeNamespace samples = new CodeNamespace("Samples");
graph.Namespaces.Add(samples);
CodeTypeDeclaration sampleClass = new CodeTypeDeclaration("Customer");
samples.Types.Add(sampleClass);
The generated CSharp source code for the sample code above:
using System;
namespace Samples {
public class Customer {
}
}
The CSharp tweak adds a "dummy" namespace with an empty name to the CodeCompileUnit,
it then adds the namespace imports to the dummy namespace. From then on the code
is the same as the first example, the "Samples" namespace is created and added to
the CodeCompileUnit and the "Customer" class is created and added to
the "Samples" namespace. As demonstrated in the sample output above the tweak generates
source code that will be more familiar to CSharp developers than the first example.
Generating an Interface
As demonstrated in the previous section creating an instance of the CodeTypeDeclaration
class using the type name constructor and leaving its properties at their default
values generates a public class. We only need to make a small adjustment to the
generation code to generate a public interface rather than a public class.
Sample code for generating an interface:
CodeCompileUnit graph = new CodeCompileUnit();
CodeNamespace samples = new CodeNamespace("Samples");
samples.Imports.Add(new CodeNamespaceImport("System"));
graph.Namespaces.Add(samples);
CodeTypeDeclaration sampleClass = new CodeTypeDeclaration("IReportWriter");
sampleClass.IsInterface = true;
samples.Types.Add(sampleClass);
The generated CSharp source code for the example IReportWriter interface:
using System;
namespace Samples {
public interface IReportWriter {
}
}
The generated Visual Basic source code for the example IReportWriter interface:
Imports System
Public Interface IReportWriter
End Interface
The sample generation code above creates an instance of the CodeTypeDeclaration
class using the type name constructor, it then sets the IsInterface property to
true to generate a public interface.
Generating a Struct
As demonstrated in the Generating a Class section creating an instance of
the CodeTypeDeclaration class using the type name constructor and leaving its
properties at their default values generates a public class. We only need to make
a small adjustment to the generation code to generate a public struct
(Structure in Visual Basic) rather than a public class.
Sample code for generating a struct:
CodeCompileUnit graph = new CodeCompileUnit();
CodeNamespace samples = new CodeNamespace("Samples");
samples.Imports.Add(new CodeNamespaceImport("System"));
graph.Namespaces.Add(samples);
CodeTypeDeclaration sampleClass = new CodeTypeDeclaration("CustomerId");
sampleClass.IsStruct = true;
samples.Types.Add(sampleClass);
The generated CSharp source code for the example CustomerId struct:
namespace Samples {
public struct CustomerId {
}
}
The generated Visual Basic source code for the example CustomerId struct:
Imports System
Public Structure CustomerId
End Structure
The sample generation code above creates an instance of the CodeTypeDeclaration
class using the type name constructor, it then sets the IsStruct property to true
to generate a public struct.
Generating an Enum
As demonstrated in the Generating a Class section creating an instance of
the CodeTypeDeclaration class using the type name constructor and leaving its
properties at their default values generates a public class. We only need to
make a small adjustment to the generation code to generate a public enum rather
than a public class.
Sample code for generating an enum:
CodeCompileUnit graph = new CodeCompileUnit();
CodeNamespace samples = new CodeNamespace("Samples");
samples.Imports.Add(new CodeNamespaceImport("System"));
graph.Namespaces.Add(samples);
CodeTypeDeclaration sampleClass = new CodeTypeDeclaration("AccountType");
sampleClass.IsEnum = true;
samples.Types.Add(sampleClass);
The generated CSharp source code for the example AccountType enum:
using System;
namespace Samples {
public enum AccountType {
}
}
The generated Visual Basic source code for the example AccountType enum:
Imports System
Public Enum AccountType
End Enum
The sample generation code above creates an instance of the CodeTypeDeclaration
class using the type name constructor, it then sets the IsEnum property to true
to generate a public enum.
By default, enumerations are typed as Int32, however you can specify a different
type after creating the CodeTypeDelaration instance by adding the required type
to the BaseTypes property.
Sample code for generating an Int64 enum:
CodeCompileUnit graph = new CodeCompileUnit();
CodeNamespace samples = new CodeNamespace("Samples");
samples.Imports.Add(new CodeNamespaceImport("System"));
graph.Namespaces.Add(samples);
CodeTypeDeclaration sampleClass = new CodeTypeDeclaration("AccountType");
sampleClass.IsEnum = true;
sampleClass.BaseTypes.Add(new CodeTypeReference(typeof(long)));
samples.Types.Add(sampleClass);
The generated CSharp source code for the example AccountType Int64 enum:
using System;
namespace Samples {
public enum AccountType : long {
}
}
The generated Visual Basic source code for the example AccountType Int64 enum:
Imports System
Public Enum AccountType As Long
End Enum
Generating an Open Generic Type
To create a generic type, we will use a class as an example here, we need to add
the relevant type parameters using the CodeTypeDeclaration instance's
TypeParameters property. The TypeParameters property references
a collection of System.CodeDom.CodeTypeParameter instances. In the example we
will just set the Name property (via the constructor) of the CodeTypeParameter
instance but you can also assign any constraints for the parameter.
Note
To generate a generic interface, set the IsInterface property to true
after creating the CodeTypeDelcaration instance. To generate a generic
struct (Structure in Visual Basic) set the IsStruct
property to true.
Sample code for generating a generic class:
CodeCompileUnit graph = new CodeCompileUnit();
CodeNamespace samples = new CodeNamespace("Samples");
samples.Imports.Add(new CodeNamespaceImport("System"));
graph.Namespaces.Add(samples);
CodeTypeDeclaration sampleClass = new CodeTypeDeclaration("ReportWriter");
sampleClass.TypeParameters.Add(new CodeTypeParameter("T"));
samples.Types.Add(sampleClass);
The generated CSharp source code for the example generic ReportWriter class:
using System;
namespace Samples {
public class ReportWriter<T>
{
}
}
The generated Visual Basic source code for the example generic ReportWriter class:
Imports System
Public Class ReportWriter(Of T)
End Class
The sample generation code above creates an instance of the CodeTypeDeclaration
class using the type name constructor, it then adds a single type parameter which,
by convention, is named "T" to define a single letter type parameter.
Specifying the Visibility and Scope of a Type
So far we have been creating and generating public types e.g. class, interface,
struct, etc. which will, typically, be the option required most often. We can now
move on to demonstrate how to specify the visibility and scope of a type, both of
which are specified using the attributes of the CodeTypeDeclaration class.
Sounds simple, doesn't it? Not so, the CodeTypeDeclaration class inherits
an Attributes property from its CodeTypeMember base class but it
also implements its own TypeAttributes property. The Attributes
property is typed as MemberAttributes which is found in
the System.CodeDom namespace whilst the TypeAttributes property is
typed as TypeAttributes which is found in the System.Reflection
namespace. To complicate matters further these enumerated types use different terminology to
define the visibility of a type, MemberAttributes uses the term "Access"
to specify public, private, family, etc. whilst TypeAttributes uses the
term "Visibility" to specify the same values.
Note
When setting the attributes of the CodeTypeDeclaration
class you should use the TypeAttributes property and therefore specify
the attributes using the System.Reflection.TypeAttributes enumeration.
That is why we are using the term "Visibility" in this section whereas
we will use the term "Access" when discussing the attributes of type members!
Sample code for generating an internal class:
CodeCompileUnit graph = new CodeCompileUnit();
CodeNamespace samples = new CodeNamespace("Samples");
samples.Imports.Add(new CodeNamespaceImport("System"));
graph.Namespaces.Add(samples);
CodeTypeDeclaration sampleClass = new CodeTypeDeclaration("Customer");
sampleClass.TypeAttributes =
(sampleClass.TypeAttributes & ~TypeAttributes.VisibilityMask) |
TypeAttributes.NotPublic;
samples.Types.Add(sampleClass);
The generated CSharp source code for the example internal Customer class:
using System;
namespace Samples {
internal class Customer {
}
}
The generated Visual Basic source code for the example internal Customer class:
Imports System
Friend Class Customer
End Class
When setting the visibility flags of the CodeTypeDeclaration class you must use
the VisibilityMask enumerated value to mask out the visibility flags before setting
the new flags otherwise you will clear all of the other flags.
The TypeAttributes property is also used to specify the scope of a type e.g. abstract,
sealed. etc.
Sample code for generating a public abstract class:
CodeCompileUnit graph = new CodeCompileUnit();
CodeNamespace samples = new CodeNamespace("Samples");
samples.Imports.Add(new CodeNamespaceImport("System"));
graph.Namespaces.Add(samples);
CodeTypeDeclaration sampleClass = new CodeTypeDeclaration("Customer");
sampleClass.TypeAttributes =
sampleClass.TypeAttributes | TypeAttributes.Abstract;
samples.Types.Add(sampleClass);
The generated CSharp source code for the example abstract Customer class:
using System;
namespace Samples {
public abstract class Customer {
}
}
The generated Visual Basic source code for the example abstract Customer class:
Imports System
Public MustInherit Class Customer
End Class
Sample code for generating a public sealed class:
CodeCompileUnit graph = new CodeCompileUnit();
CodeNamespace samples = new CodeNamespace("Samples");
samples.Imports.Add(new CodeNamespaceImport("System"));
graph.Namespaces.Add(samples);
CodeTypeDeclaration sampleClass = new CodeTypeDeclaration("Customer");
sampleClass.TypeAttributes =
sampleClass.TypeAttributes | TypeAttributes.Sealed;
samples.Types.Add(sampleClass);
The generated CSharp source code for the example sealed Customer class:
using System;
namespace Samples {
public sealed class Customer {
}
}
The generated Visual Basic source code for the example sealed Customer class:
Imports System
Public NotInheritable Class Customer
End Class
Scope attributes can be set directly with the TypeAttributes enumeration, however
this is not the case when using the MemberAttributes enumeration to set the scope
flags on a type member, you have to use the MemberAttributes.ScopeMask enumerated
value to avoid clearing existing flags.
Specifying the Base Types of a Type
To specify the base type or types of a type, which can be a class and/or one or
more interfaces, we need to add one or more System.CodeDom.CodeTypeReference instances
to the CodeTypeDeclaration instance's BaseTypes property.
The BaseTypes property references a collection of CodeTypeReference instances.
Sample code for generating a class with that inherits
a base class:
CodeCompileUnit graph = new CodeCompileUnit();
CodeNamespace samples = new CodeNamespace("Samples");
samples.Imports.Add(new CodeNamespaceImport("System"));
graph.Namespaces.Add(samples);
CodeTypeDeclaration sampleClass = new CodeTypeDeclaration("Manager");
sampleClass.BaseTypes.Add(new CodeTypeReference("Employee"));
samples.Types.Add(sampleClass);
The generated CSharp source code for the example class with inherited base class:
using System;
namespace Samples {
public class Manager : Employee {
}
}
The generated Visual Basic source code for the example class with inherited base
class:
Imports System
Public Class Manager
Inherits Employee
End Class
Sample code for generating a class that inherits an interface:
CodeCompileUnit graph = new CodeCompileUnit();
CodeNamespace samples = new CodeNamespace("Samples");
samples.Imports.Add(new CodeNamespaceImport("System"));
graph.Namespaces.Add(samples);
CodeTypeDeclaration sampleClass = new CodeTypeDeclaration("Employee");
sampleClass.BaseTypes.Add(new CodeTypeReference(typeof(IComparable)));
samples.Types.Add(sampleClass);
The generated CSharp source code for the example class with inherited interface:
using System;
namespace Samples {
public class Employee : System.IComparable {
}
}
The generated Visual Basic source code for the example class with inherited interface:
Imports System
Public Class Employee
Implements System.IComparable
End Class
Note
Generating source code for multiple languages:
When specifying just an interface as a base type, you should use the Type
(use typeof or GetType in Visual Basic) of the interface
when constructing the CodeTypeReference instance. If you cannot use
a Type then you should add System.Object as a base type
before adding the interface, for example:
sampleClass.BaseTypes.Add(new CodeTypeReference(typeof(object)));
sampleClass.BaseTypes.Add(new CodeTypeReference("ISomeInterface"));
In the .Net Framework prior to version 2.0 you had to add an Object base class before
the interface.
If you do not use this technique some language code providers will not interpret
your intentions correctly.
Sample code for generating a class that inherits a base
class and an interface:
CodeCompileUnit graph = new CodeCompileUnit();
CodeNamespace samples = new CodeNamespace("Samples");
samples.Imports.Add(new CodeNamespaceImport("System"));
graph.Namespaces.Add(samples);
CodeTypeDeclaration sampleClass = new CodeTypeDeclaration("Manager");
sampleClass.BaseTypes.Add(new CodeTypeReference("Employee"));
sampleClass.BaseTypes.Add(new CodeTypeReference("ISupervisor"));
samples.Types.Add(sampleClass);
The generated CSharp source code for the example class with inherited base class
and interface:
using System;
namespace Samples {
public class Manager : Employee, ISupervisor {
}
}
The generated Visual Basic source code for the example class with inherited base
class and interface:
Imports System
Public Class Manager
Inherits Employee
Implements ISupervisor
End Class
Specifying a Generic Type As a Base Type
To specify a generic type as a base class we need to set the TypeArguments property
of the CodeTypeReference instance that is to represent the generic base type. We
can do this using the CodeTypeReference constructor that accepts an array
of CodTypeReference instances as type arguments, as in the example below or we
can construct the CodeTypeReference using the constructor that accepts
a Type parameter.
Sample code for generating a class that inherits a closed
constructed generic base class:
CodeCompileUnit graph = new CodeCompileUnit();
CodeNamespace samples = new CodeNamespace("Samples");
samples.Imports.Add(new CodeNamespaceImport("System"));
samples.Imports.Add(new CodeNamespaceImport("System.Collections.ObjectModel"));
graph.Namespaces.Add(samples);
CodeTypeDeclaration sampleClass = new CodeTypeDeclaration("CustomerCollection");
sampleClass.BaseTypes.Add(new CodeTypeReference("Collection",
new CodeTypeReference[] { new CodeTypeReference("Customer") }));
samples.Types.Add(sampleClass);
The generated CSharp source code for the example class with an inherited closed
constructed generic base class:
using System;
using System.Collections.ObjectModel;
namespace Samples {
public class CustomerCollection : Collection<Customer> {
}
}
The generated Visual Basic source code for the example class with an inherited closed
constructed generic base class:
Imports System
Imports System.Collections.ObjectModel
Public Class CustomerCollection
Inherits Collection(Of Customer)
End Class
When creating a CodeTypeReference instance that is to represent a generic type you
should either use the constructor that accepts a Type parameter or the constructor
that accepts an array of CodeTypeReference instances that represent the type arguments
of the generic type.
Generating Nested Types
To nest a type within a type we need to create a CodeTypeDeclaration instance to
represent the nested type and then add it to the Members property of the outer type.
The requirement to support nested types is the reason that the CodeTypeDeclaration
class derives from the CodeTypeMember class i.e. an instance of
the CodeTypeDeclaration class can represent a type or it can represent a member
of a type or a nested type.
Sample code for generating a class that includes a nested
class as a member:
CodeCompileUnit graph = new CodeCompileUnit();
CodeNamespace samples = new CodeNamespace("Samples");
samples.Imports.Add(new CodeNamespaceImport("System"));
graph.Namespaces.Add(samples);
CodeTypeDeclaration sampleClass = new CodeTypeDeclaration("Customer");
samples.Types.Add(sampleClass);
CodeTypeDeclaration nestedClass = new CodeTypeDeclaration("Address");
nestedClass.TypeAttributes =
(nestedClass.TypeAttributes & ~TypeAttributes.VisibilityMask) |
TypeAttributes.NestedPrivate;
sampleClass.Members.Add(nestedClass);
The generated CSharp source code for the example class with a nested class member:
using System;
namespace Samples {
public class Customer {
private class Address {
}
}
}
The generated Visual Basic source code for the example class with a nested class
member:
Imports System
Public Class Customer
Private Class Address
End Class
End Class
As demonstrated in the example code above it is the "visibility" of a type that
identifies it as a nested type when it is added, as a member, to an outer type.
In the example, we have set the TypeAttributes visibility flag
to NestedPrivate but you can specify NestedAssembly,
NestedFamANDAssembly, NestedFamily,
NestedFamORAssembly or NestedPublic.
You can nest a struct within a class, by setting the IsStruct property of
the CodeTypeDeclaration instance, representing the nested type, to true.
You can also nest a class within a struct although this would be a
fairly unusual requirement.
Generating a Private Enum Within a Type
Nesting, as demonstrated in the previous section, can also be used to include an
enum as a private member of a type. Although, typically, an enum would be added
as a public type to a namespace there are occasions where it is useful or necessary
to define an enum as a private member of, for example, a class.
Sample code for generating a class that includes a private
nested enum as a member:
CodeCompileUnit graph = new CodeCompileUnit();
CodeNamespace samples = new CodeNamespace("Samples");
samples.Imports.Add(new CodeNamespaceImport("System"));
graph.Namespaces.Add(samples);
CodeTypeDeclaration sampleClass = new CodeTypeDeclaration("Customer");
samples.Types.Add(sampleClass);
CodeTypeDeclaration nestedClass = new CodeTypeDeclaration("AccountType");
nestedClass.IsEnum = true;
nestedClass.TypeAttributes =
(nestedClass.TypeAttributes & ~TypeAttributes.VisibilityMask) |
TypeAttributes.NestedPrivate;
sampleClass.Members.Add(nestedClass);
The generated CSharp source code for the example class with a private nested enum
member:
using System;
namespace Samples {
public class Customer {
private enum AccountType {
}
}
}
The generated Visual Basic source code for the example class with a private nested
enum member:
Imports System
Public Class Customer
Private Enum AccountType
End Enum
End Class
For details of how to add enumerated members to an enum see the
Generating Enumerated Members section.
Generating Fields
To generate a field we need to add an instance of the CodeMemberField class to the
CodeTypeDeclaration instance's Members property. Fields can be
created as just a declaration or they can be declared and initialized, fields may be private
(the default), protected, internal or public and may also be declared and initialized
as constant.
Sample code for generating fields:
CodeCompileUnit graph = new CodeCompileUnit();
CodeNamespace samples = new CodeNamespace("Samples");
samples.Imports.Add(new CodeNamespaceImport("System"));
graph.Namespaces.Add(samples);
CodeTypeDeclaration sampleClass = new CodeTypeDeclaration("Customer");
samples.Types.Add(sampleClass);
// Initialized field...
CodeMemberField lockTarget = new CodeMemberField(typeof(object), "_lockTarget");
lockTarget.InitExpression = new CodeObjectCreateExpression(typeof(object),
new CodeExpression[] {});
sampleClass.Members.Add(lockTarget);
// Field declarations...
sampleClass.Members.Add(new CodeMemberField(typeof(int), "_id"));
sampleClass.Members.Add(new CodeMemberField(typeof(string), "_name"));
// Const fields...
CodeMemberField accountBusiness = new CodeMemberField(typeof(string),
"ACCOUNT_BUSINESS");
accountBusiness.InitExpression = new CodePrimitiveExpression("Business");
accountBusiness.Attributes =
(accountBusiness.Attributes & ~MemberAttributes.ScopeMask) |
MemberAttributes.Const;
sampleClass.Members.Add(accountBusiness);
CodeMemberField accountPersonal = new CodeMemberField(typeof(string),
"ACCOUNT_PERSONAL");
accountPersonal.InitExpression = new CodePrimitiveExpression("Personal");
accountPersonal.Attributes =
(accountPersonal.Attributes & ~MemberAttributes.ScopeMask) |
MemberAttributes.Const;
sampleClass.Members.Add(accountPersonal);
The generated CSharp source code for the example class with fields:
using System;
namespace Samples {
public class Customer {
private object _lockTarget = new object();
private int _id;
private string _name;
private const string ACCOUNT_BUSINESS = "Business";
private const string ACCOUNT_PERSONAL = "Personal";
}
}
The generated Visual Basic source code for the example class with fields:
Imports System
Public Class Customer
Private _lockTarget As Object = New Object
Private _id As Integer
Private _name As String
Private Const ACCOUNT_BUSINESS As String = "Business"
Private Const ACCOUNT_PERSONAL As String = "Personal"
End Class
The example code above demonstrates generating an initialized field (_lockTarget)
by assigning a CodeObjectCreateExpression instance to the
CodeMemberField instance's InitExpression property. The
first CodeObjectCreateExpression constructor parameter
specifies the Type of the object to create, Object in the example
and the second, CodeExpression array, parameter specifies the parameters required
by the object's constructor, in the example above the default constructor is required so
an empty array is specified.
The next two fields in the example above are simple declarations, however, you should
note that the CodeMemberField constructor that accepts a Type is used to create
the fields.
The final two fields in the example above are also initialized fields, however they
are declared and initialized as constant fields. The CodeMemberField instance's
InitExpression property is used to assign an instance of the
CodePrimitiveExpression class initialized using a literal string. We then have
to set the Attributes property scope flags to MemberAttributes.Const
ensuring that we mask out the scope flags using the MemberAttributes.ScopeMask. If
you want to create a public, rather than the default private field set
the Attributes property MembersAttribute.Public access
flag whilst masking out the access flags using the MemberAttributes.AccessMask.
Generating Enumerated Members
We can also use the CodeMemberField class to add members to an enumeration or enum.
The type of an enumerated member is implied by the type of the enumeration, which
by default is Int32. Therefore, when creating instances of
the CodeMemberField class to add to an enum type we don't have
to specify a type for the field, however, typically, it is just as easy to specify a type as
not to, so you may as well, just remember to specify the enum type for each field.
Sample code for generating enum fields:
CodeCompileUnit graph = new CodeCompileUnit();
CodeNamespace samples = new CodeNamespace("Samples");
samples.Imports.Add(new CodeNamespaceImport("System"));
graph.Namespaces.Add(samples);
CodeTypeDeclaration sampleEnum = new CodeTypeDeclaration("AccountType");
sampleEnum.IsEnum = true;
samples.Types.Add(sampleEnum);
CodeMemberField business = new CodeMemberField();
business.Name = "Business";
sampleEnum.Members.Add(business);
sampleEnum.Members.Add(new CodeMemberField(typeof(int), "Personal"));
The generated CSharp source code for the example enum with fields:
using System;
namespace Samples {
public enum AccountType {
Business,
Personal,
}
}
The generated Visual Basic source code for the example enum with fields:
Imports System
Public Enum AccountType
Business
Personal
End Enum
If we don't want the first enumerated member to have the value 0 (zero) which is
the default, we can use the InitExpression property to assign a non-default value
to the first field as follows.
Sample code for generating an initialized enum field:
CodeMemberField business = new CodeMemberField();
business.Name = "Business";
business.InitExpression = new CodePrimitiveExpression(1);
sampleEnum.Members.Add(business);
The generated CSharp source code for the example initialized enum field:
using System;
namespace Samples {
public enum AccountType {
Business = 1,
Personal,
}
}
The generated Visual Basic source code for the example initialized enum field:
Imports System
Public Enum AccountType
Business = 1
Personal
End Enum
The fields we add to an enum represent the named constants or enumerator list, however,
we don't have to explicitly set the MemberAttributes.Const flag of each field.
In the generated CSharp source code you may have noticed that there is a comma after
the last field. Typically, when hand coding an enum a developer would not include
the last comma as it is not necessary, however, it is not invalid to do so and the
generated code will compile without error.
Generating Constructors
To generate a constructor we need to add an instance of the CodeConstructor class
to the CodeTypeDeclaration instance's Members property. When
creating the constructor we can use the BaseConstructorArgs property to pass
values to the base type's constructor or the ChainedConstructorArgs property
to pass values to another constructor overload. Both the BaseConstructorArgs property
and the ChainedConstructorArgs property reference a collection of CodeExpression
instances that can be used to specify either a specific value or a reference to a constructor
parameter. The CodeConsructor instance's, inherited, Parameters
property can be used to specify the constructor parameters. The Parameters property
references a collection of CodeParameterDeclarationExpression instances.
Sample code for generating constructors:
CodeCompileUnit graph = new CodeCompileUnit();
CodeNamespace samples = new CodeNamespace("Samples");
samples.Imports.Add(new CodeNamespaceImport("System"));
graph.Namespaces.Add(samples);
CodeTypeDeclaration sampleClass = new CodeTypeDeclaration("Customer");
samples.Types.Add(sampleClass);
CodeConstructor ctor = new CodeConstructor();
ctor.Attributes =
(ctor.Attributes & ~MemberAttributes.AccessMask) |
MemberAttributes.Public;
ctor.ChainedConstructorArgs.Add(new CodePrimitiveExpression(-1));
ctor.ChainedConstructorArgs.Add(new CodePropertyReferenceExpression(
new CodeTypeReferenceExpression(typeof(string)), "Empty"));
sampleClass.Members.Add(ctor);
CodeConstructor ctor1 = new CodeConstructor();
ctor1.Attributes =
(ctor1.Attributes & ~MemberAttributes.AccessMask) |
MemberAttributes.Public;
ctor1.BaseConstructorArgs.Add(new CodeSnippetExpression(string.Empty));
ctor1.Parameters.Add(new CodeParameterDeclarationExpression(typeof(int), "id"));
ctor1.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "name"));
sampleClass.Members.Add(ctor1);
The generated CSharp source code for the example class with constructors:
using System;
namespace Samples {
public class Customer {
public Customer() :
this(-1, string.Empty) {
}
public Customer(int id, string name) :
base() {
}
}
}
The generated Visual Basic source code for the example class with constructors:
Imports System
Public Class Customer
Public Sub New()
Me.New(-1, String.Empty)
End Sub
Public Sub New(ByVal id As Integer, ByVal name As String)
MyBase.New()
End Sub
End Class
The example code above demonstrates generating a default, parameterless, constructor
that passes default values to another constructor overload using the ChainedConstructorArgs
property. An instance of the CodePrimitiveExpression class is used to pass the value
-1 as the default ID to the other constructor overload and an instance of
the CodePropertyReferenceExpression class is used to pass the value of
the string.Empty field as the default name to the other constructor overload. An
instance of the CodeTypeReferenceExpression class is used to specify
the string.Empty field for the CodePropertyReferenceExpression
instance.
The second constructor is generated with two parameters by adding two instances
of the CodeParameterDeclarationExpression class to the, inherited,
Parameters property. The first parameter is an integer named id and
the second a string named name. The BaseConstructorArgs property is
used to call the base class default constructor. To call the base class constructor with no
arguments we have used an instance of the CodeSnippetExpression class with the
value Empty. The same technique can be used with
the ChainedConstructorArgs property to call a constructor overload with
no arguments.
Both constructors are generated with public access by setting their Attributes property
access flags to MemberAttributes.Public whilst masking out the access flags using
the MemberAttributes.AccessMask.
The CodeConstructor class is used to create and generate an instance constructor
for a type, to generate a static constructor we have to create an instance of the
CodeTypeConstructor class and add it to the CodeTypeDeclaration
instance's Members collection. A static constructor has a very simple
signature with no parameters, base class or overload calls, so adding a static constructor
to a type is very simple:
sampleClass.Members.Add(new CodeTypeConstructor());
The generated CSharp source code for the example class with a static constructor:
using System;
namespace Samples {
public class Customer {
static Customer() {
}
}
}
The generated Visual Basic source code for the example class with a static constructor:
Imports System
Public Class Customer
Shared Sub New()
End Sub
End Class
Note
When creating objects to add to the CodeDOM and when setting the properties of a
CodeDOM object, typically, you have to make the choice of whether to create an object
in one using the constructor or to create an instance and then set its properties.
To a certain extent the choice boils down to personal preference, however, in a
lot of cases using the constructor can lead to a very long line of code with a lot
of nested brackets because, typically, one object is created using one or more other
objects which also have to be constructed in the same line of code.
Until you become familiar with the classes in the System.CodeDom namespace we think
that, where possible, it is easier to create an object using the default or the
least complex constructor and then set its properties. Creating multiple objects
within a single outer constructor call can be slightly bewildering when you are
new to the CodeDOM.
Generating Properties
To generate a property we need to add an instance of the CodeMemberProperty class
to the CodeTypeDeclaration instance's Members property.
The CodeMemberProperty class only has a default, parameterless, constructor so we
have to create an instance and then set its properties. We need to specify a name, a type and
the property access and then specify the get and/or the set statements.
To keep the sample code short and to concentrate on the creation of a property we
have omitted the code to create the namespace and class. The omitted code creates
a CodeTypeDeclaration instance variable named "sampleClass".
Sample code for generating a read only property:
CodeMemberProperty id = new CodeMemberProperty();
id.Name = "Id";
id.Type = new CodeTypeReference(typeof(int));
id.Attributes =
(id.Attributes & ~MemberAttributes.AccessMask) |
MemberAttributes.Public;
id.GetStatements.Add(new CodeThrowExceptionStatement(
new CodeObjectCreateExpression(
new CodeTypeReference(typeof(NotSupportedException)),
new CodeExpression[] { })));
sampleClass.Members.Add(id);
The generated CSharp source code for the example class with a read only property:
using System;
namespace Samples {
public class Customer {
public int Id {
get {
throw new System.NotSupportedException();
}
}
}
}
The generated Visual Basic source code for the example class with a read only property:
Imports System
Public Class Customer
Public ReadOnly Property Id() As Integer
Get
Throw New System.NotSupportedException
End Get
End Property
End Class
The example generation code should be familiar down to the point where it creates
the get statements. Up to now we have, mostly, been demonstrating the creation of
types and member signatures, this is the first time that we have encountered the
body or code statements of a member. Typically, when generating class templates,
code statements are not included, the developer adds the implementation. However,
if we generate a property get member or a non-void method (Function in Visual Basic)
we have to include either a return statement or throw an exception, otherwise the
generated source code will not compile.
Whilst demonstrating the creation and generation of types and their members we are,
in effect, generating template code, we will discuss generating statements and expressions
later in the article. We do, however, want the generated sample code to compile,
so we will generate a throw exception statement for property getters and non-void
methods.
We prefer to generate a NotSupportedException rather than a NotImplementedException
because the NotSupportedException has a, system supplied, default error message
and the NotImplementedException does not. Therefore, we do not have to
create CodeExpression instances to provide constructor arguments when creating
the exception, we can specify an empty array!
To create the exception throw statement for the property getter we use an instance
of the CodeThrowExceptionStatement class. This class is a glowing example of the
complexity of construction implemented by some of the System.CodeDom classes. The
documentation for the CodeThrowExceptionStatement class constructor looks fairly
benign, the constructor we require simply specifies an instance of the CodeExpression
class as its only parameter. However, we need to specify an instance of
the CodeObjectCreateExpression class to create and generate the throw statement
and to construct an instance of this class we need to specify the type we want to create and
an array of CodeExpression instances to specify the constructor arguments.
By default, properties are generated with private access, therefore, if we want
a public property we have to set the MemberAttributes.Public access flag. To create
and generate a read only property is very straight forward, if we don't assign a
value to the SetStatements property we generate a read only property.
Sample code for generating a read write property:
CodeMemberProperty name = new CodeMemberProperty();
name.Name = "Name";
name.Type = new CodeTypeReference(typeof(string));
name.Attributes =
(name.Attributes & ~MemberAttributes.AccessMask) |
MemberAttributes.Public;
name.GetStatements.Add(new CodeThrowExceptionStatement(
new CodeObjectCreateExpression(
new CodeTypeReference(typeof(NotSupportedException)),
new CodeExpression[] { })));
name.SetStatements.Add(new CodeSnippetExpression(string.Empty));
sampleClass.Members.Add(name);
The generated CSharp source code for the example class with a read write property:
using System;
namespace Samples {
public class Customer {
public string Name {
get {
throw new System.NotSupportedException();
}
set {
;
}
}
}
}
The generated Visual Basic source code for the example class with a read write property:
Imports System
Public Class Customer
Public Property Name() As String
Get
Throw New System.NotSupportedException
End Get
Set
End Set
End Property
End Class
The example generation code creates a throw exception statement for the GetStatements
property and then assigns an instance of the CodeSnippetExpression class, initialized
to Empty, to the SetStatements property to create an empty setter template.
Typically, properties do not have parameters, however, the CodeMemberProperty class
does have a Parameters property which references a collection
of CodeParameterDeclarationExpression instances. The Parameters property
is implemented to facilitate the creation and generation of an indexer property. To create an
indexer, the CodeMemberProperty instance's, inherited, Name
property must be set to the "special" value of "Item" and
the Parameters property must contain one or more parameter references.
To keep the sample code short and to concentrate on the creation of an indexer property
we have omitted the code to create the namespace and class. The omitted code creates
a CodeTypeDeclaration instance that represents a collection class derived
from Collection<T> and assigns the instance to a variable named "sampleClass".
Sample code for generating an Item or indexer property:
CodeMemberProperty item = new CodeMemberProperty();
item.Name = "Item";
item.Type = new CodeTypeReference("Customer");
item.Parameters.Add(new CodeParameterDeclarationExpression(
new CodeTypeReference(typeof(string)), "name"));
item.Attributes =
(item.Attributes & ~MemberAttributes.AccessMask) |
MemberAttributes.Public;
item.GetStatements.Add(new CodeThrowExceptionStatement(
new CodeObjectCreateExpression(
new CodeTypeReference(typeof(NotSupportedException)),
new CodeExpression[] { })));
sampleClass.Members.Add(item);
The generated CSharp source code for the example class with an indexer property:
using System;
using System.Collections.ObjectModel;
namespace Samples {
public class CustomerCollection : Collection<Customer> {
public Customer this[string name] {
get {
throw new System.NotSupportedException();
}
}
}
}
The generated Visual Basic source code for the example class with an indexer property:
Imports System
Imports System.Collections.ObjectModel
Public Class CustomerCollection
Inherits Collection(Of Customer)
Public Default ReadOnly Property Item(ByVal name As String) As Customer
Get
Throw New System.NotSupportedException
End Get
End Property
End Class
The example generation code creates a CodeMemberProperty instance, assigns
the "special" name value "Item" to denote that the property is an indexer
and then adds a CodeParameterDeclarationExpression instance to
the Parameters collection to define a String parameter named "name".
In this example the indexer is read only, however, we could have generated a setter
by assigning a value to the SetStatements property.
A property may implicitly or explicitly implement an interface member and the CodeMemberProperty
class has an ImplementationTypes property that references a collection of CodeTypeReference
instances that represent the interface types, implicitly, implemented by the property
and a PrivateImplementationType property that references a CodeTypeReference instance
representing the interface type, explicitly, implemented by a private property.
Generating Events
To generate an event we need to add an instance of the CodeMemberEvent class to
the CodeTypeDeclaration instance's Members property.
The CodeMemberEvent class only has a default, parameterless, constructor so we
have to create an instance and then set its properties. We need to specify an event name and
an event type and preferably we should specify the generic System.EventHandler
delegate as the type.
To keep the sample code short and to concentrate on the creation of an event we
have omitted the code to create the namespace and class. The omitted code creates
a CodeTypeDeclaration instance variable named "sampleClass".
Sample code for generating an event:
CodeMemberEvent changedEvent = new CodeMemberEvent();
changedEvent.Name = "BalanceChanged";
CodeTypeReference eventType = new CodeTypeReference(typeof(EventHandler));
eventType.TypeArguments.Add(new CodeTypeReference(
new CodeTypeParameter("BalanceChangedEventArgs")));
changedEvent.Type = eventType;
changedEvent.Attributes =
(changedEvent.Attributes & ~MemberAttributes.AccessMask) |
MemberAttributes.Public;
sampleClass.Members.Add(changedEvent);
The generated CSharp source code for the example class with an event:
using System;
namespace Samples {
public class Customer {
public event System.EventHandler<BalanceChangedEventArgs> BalanceChanged;
}
}
The generated Visual Basic source code for the example class with an event:
Imports System
Public Class Customer
Public Event BalanceChanged As System.EventHandler(Of BalanceChangedEventArgs)
End Class
The example generation code creates a CodeMemberEvent instance and assigns the name
of the event as "BalanceChanged". The code then builds the type of the event by
specifying the generic System.EventHandler with a generic type parameter value of
"BalanceChangedEventArgs". To specify an event that does not pass event data to
the handler use the EventArgs type as the generic type parameter or create
the CodeTypeReference instance as follows:
eventType = new CodeTypeReference(typeof(EventHandler<EventArgs>));
By default, events are generated with private access, therefore, if we want a public
event we have to set the MemberAttributes.Public access flag.
An event may implicitly or explicitly implement an interface member and the CodeMemberEvent
class has an ImplementationTypes property that references a collection of CodeTypeReference
instances that represent the interface types, implicitly, implemented by the event
and a PrivateImplementationType property that references a CodeTypeReference instance
representing the interface type, explicitly, implemented by a private event.
Generating Methods
To generate a method we need to add an instance of the CodeMemberMethod class to
the CodeTypeDeclaration instance's Members property.
The CodeMemberMethod class only has a default, parameterless, constructor so we
have to create an instance and then set its properties. We need to specify a name, parameters,
return type and the method access and then specify the statements.
To keep the sample code short and to concentrate on the creation of a method we
have omitted the code to create the namespace and class. The omitted code creates
a CodeTypeDeclaration instance variable named "sampleClass".
Sample code for generating a void method:
CodeMemberMethod update = new CodeMemberMethod();
update.Name = "UpdateCreditLimit";
update.Parameters.Add(
new CodeParameterDeclarationExpression(typeof(decimal),
"limit"));
update.ReturnType = new CodeTypeReference("System.Void");
update.Attributes =
(update.Attributes & ~MemberAttributes.AccessMask) |
MemberAttributes.Public;
sampleClass.Members.Add(update);
The generated CSharp source code for the example class with a void method:
using System;
namespace Samples {
public class Customer {
public void UpdateCreditLimit(decimal limit) {
}
}
}
The generated Visual Basic source code for the example class with a void method:
Imports System
Public Class Customer
Public Sub UpdateCreditLimit(ByVal limit As Decimal)
End Sub
End Class
Sample code for generating a non-void method:
CodeMemberMethod addTo = new CodeMemberMethod();
addTo.Name = "AddToBalance";
addTo.Parameters.Add(
new CodeParameterDeclarationExpression(typeof(int),
"id"));
addTo.Parameters.Add(
new CodeParameterDeclarationExpression(typeof(decimal),
"sale"));
addTo.Statements.Add(new CodeThrowExceptionStatement(
new CodeObjectCreateExpression(
new CodeTypeReference(typeof(NotSupportedException)),
new CodeExpression[] { })));
addTo.ReturnType = new CodeTypeReference(typeof(decimal));
addTo.Attributes =
(addTo.Attributes & ~MemberAttributes.AccessMask) |
MemberAttributes.Public;
sampleClass.Members.Add(addTo);
The generated CSharp source code for the example class with a non-void method:
using System;
namespace Samples {
public class Customer {
public decimal AddToBalance(int id, decimal sale) {
throw new System.NotSupportedException();
}
}
}
The generated Visual Basic source code for the example class with a non-void method:
Imports System
Public Class Customer
Public Function AddToBalance(ByVal id As Integer, ByVal sale As Decimal) As Decimal
Throw New System.NotSupportedException
End Function
End Class
The example generation code creates a CodeMemberMethod instance and assigns the
name of the method, the parameters and the return type. For void (Sub in Visual
Basic) methods the System.Void type is specified as the return type.
By default, methods are generated with private access, therefore, if we want a public
method we have to set the MemberAttributes.Public access flag.
A method may implicitly or explicitly implement an interface member and the CodeMemberMethod
class has an ImplementationTypes property that references a collection
of CodeTypeReference instances that represent the interface types, implicitly,
implemented by the method and a PrivateImplementationType property that references
a CodeTypeReference instance representing the interface type, explicitly,
implemented by a private method.
The CodeMemberMethod class has a TypeParameters property that references
a collection of CodeTypeParameter instances. The TypeParameters
property can be used to specify the type parameters when generating a generic method.
Specifying the Access and Scope of a Type Member
The CodeMemberEvent, CodeMemberField, CodeMemberMethod
and CodeMemberProperty classes are all derived from the CodeTypeMember
base class and the CodeConstructor class derives, indirectly, from
CodeTypeMember through its base class CodeMemberMethod.
All of these classes inherit an Attributes property from CodeTypeMember
which is typed as MemberAttributes. The MemberAttributes enumeration
defines members to set member access, public, private, family, assembly, etc. and members to set member
scope, abstract, final, static, override, etc.
The MemberAttributes enumeration is a bit of a weird beast, it looks like a bit
field, it is used like a bit field but it is not decorated with the FlagsAttribute.
However, we reckon if it looks like a duck, walks like a duck and quacks like a
duck then the chances are, it is a duck!
When we create an instance of the one of the CodeMember* classes or
the CodeConstructor class, the Attributes property is initialized
to a default value of (Private | Final). Therefore, for example, creating and
generating a method using the default Attributes value as follows:
CodeMemberMethod test = new CodeMemberMethod();
test.Name = "TestMethod";
test.ReturnType = new CodeTypeReference("System.Void");
produces the following generated method signature:
CSharp:
private void TestMethod() {
}
Visual Basic:
Private Sub TestMethod()
End Sub
If we, explicitly, set the access to Public we will clear the Final scope flag and
generate a virtual (Overridable in Visual Basic) method as follows:
test.Attributes = MemberAttributes.Public;
produces the following generated method signature:
CSharp:
public virtual void TestMethod() {
}
Visual Basic:
Public Overridable Sub TestMethod()
End Sub
If, however, we want a public method but we don't want it to be virtual, we have
to use the MemberAttributes.AccessMask enumerated value to mask out the access flag
and leave the Final scope flag bit set, as follows:
test.Attributes =
(test.Attributes & ~MemberAttributes.AccessMask) |
MemberAttributes.Public;
produces the following generated method signature:
CSharp:
public void TestMethod() {
}
Visual Basic:
Public Sub TestMethod()
End Sub
Explicitly setting a scope flag also clears the other flags, so if we want to generate
an override (Overrides in Visual Basic) method, for example, we
have to use the MemberAttributes.ScopeMask enumerated value to mask out the scope
flag, as follows:
test.Attributes =
(test.Attributes & ~MemberAttributes.ScopeMask) |
MemberAttributes.Override;
produces the following generated method signature:
CSharp:
private override void TestMethod() {
}
Visual Basic:
Private Overrides Sub TestMethod()
End Sub
However, we are unlikely to want a private override! Therefore to generate
a protected override we are not bothered about clearing the default flags, so
we can, explicitly, set the required flags, as follows:
test.Attributes = MemberAttributes.Family | MemberAttributes.Override;
produces the following generated method signature:
CSharp:
protected override void TestMethod() {
}
Visual Basic:
Protected Overrides Sub TestMethod()
End Sub
When setting an access flag, use the MemberAttributes.AccessMask enumerated value
if you want to change only the access flag and leave the other flags set. When setting
a scope flag, use the MemberAttributes.ScopeMask enumerated value if you want to
change only the scope flag and leave other the flags set. If you are changing both
the access and scope flags or if you specifically want to clear the, relevant, other
flags you can set the relevant flags directly.
Generating Expressions and Statements
To generate the code body for a property getter, property setter or method we need
use the classes that derive from the CodeExpression and CodeStatement
base classes. Although, as stated earlier in this article, you will, typically, have to resort
to literal code snippets when generating complex or compiler enhanced code constructs,
you can generate most of the commonly used code constructs, in a language-independent
way, with the CodeExpression and CodeStatement derived classes.
We don't have the time or the space to cover every single permutation of the expressions
and statements that can be generated, so we will use a single method implementation
to cover some of the common constructs to demonstrate the basic techniques of creating
code bodies.
To keep the sample code short and to concentrate on the creation of a code body
we have omitted the code to create the namespace and class. The omitted code creates
a CodeTypeDeclaration instance variable named "sampleClass" and adds
the following method template to it:
Sample code for generating the method template:
CodeMemberMethod test = new CodeMemberMethod();
test.Name = "CreateSalutation";
test.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string),
"title"));
test.ReturnType = new CodeTypeReference(typeof(string));
test.Attributes =
(test.Attributes & ~MemberAttributes.AccessMask) |
MemberAttributes.Public;
sampleClass.Members.Add(test);
The generated CSharp source code for the example method template:
public string CreateSalutation(string title) {
}
The generated Visual Basic source code for the example method template:
Public Function CreateSalutation(ByVal title As String) As String
End Function
The sample code will now concentrate on building the code body for the "test" method.
We will build the source code in stages but we will show the full method when it
is finished.
Sample code for generating an if statement to test the
method parameter for null:
CodeConditionStatement ifStatement = new CodeConditionStatement();
CodeBinaryOperatorExpression cond = new CodeBinaryOperatorExpression(
new CodeArgumentReferenceExpression("title"),
CodeBinaryOperatorType.IdentityEquality,
new CodePrimitiveExpression(null));
ifStatement.Condition = cond;
ifStatement.TrueStatements.Add(new CodeThrowExceptionStatement(
new CodeObjectCreateExpression(
new CodeTypeReference(typeof(ArgumentNullException)),
new CodeExpression[] { new CodePrimitiveExpression("title") })));
test.Statements.Add(ifStatement);
The generated CSharp source code for the example if statement:
if ((title == null)) {
throw new System.ArgumentNullException("title");
}
The generated Visual Basic source code for the example if statement:
If (title Is Nothing) Then
Throw New System.ArgumentNullException("title")
End If
If you are thinking that is a lot of code to generate a simple if statement then
you are starting to get the idea! The double brackets are an anomaly of the CSharp
code provider, it seems to create a bracket pair for the CodeConditionStatement
and then creates another pair for the CodeBinaryOperationExpression which looks
odd for an if statement with a single condition expression. This is another case
of generated code that is not "pretty" but is valid and compiles without error.
Sample code for generating and initializing two local
variables:
CodeVariableDeclarationStatement salutation =
new CodeVariableDeclarationStatement(
new CodeTypeReference(typeof(string)), "salutation");
salutation.InitExpression = new CodePropertyReferenceExpression(
new CodeTypeReferenceExpression(typeof(string)), "Empty");
test.Statements.Add(salutation);
CodeVariableDeclarationStatement name =
new CodeVariableDeclarationStatement(
new CodeTypeReference(typeof(string)), "name");
name.InitExpression = new CodeMethodInvokeExpression(
new CodeThisReferenceExpression(), "CreateFullName",
new CodeExpression[] { });
test.Statements.Add(name);
The generated CSharp source code for the example variable declarations:
string salutation = string.Empty;
string name = this.CreateFullName();
The generated Visual Basic source code for the example variable declarations:
Dim salutation As String = String.Empty
Dim name As String = Me.CreateFullName
Sample code for generating an if statement that builds
the return salutation value:
CodeConditionStatement ifStatement1 = new CodeConditionStatement();
CodeBinaryOperatorExpression cond1 =
new CodeBinaryOperatorExpression(
new CodePropertyReferenceExpression(
new CodeVariableReferenceExpression("name"), "Length"),
CodeBinaryOperatorType.GreaterThan,
new CodePrimitiveExpression(0));
ifStatement1.Condition = cond1;
ifStatement1.TrueStatements.Add(new CodeAssignStatement(
new CodeVariableReferenceExpression("salutation"),
new CodeMethodInvokeExpression(
new CodeTypeReferenceExpression(typeof(string)), "Concat",
new CodeExpression[] { new CodeArgumentReferenceExpression("title"),
new CodePrimitiveExpression(" "),
new CodeVariableReferenceExpression("name")})));
test.Statements.Add(ifStatement1);
The generated CSharp source code for the second if statement:
if ((name.Length > 0)) {
salutation = string.Concat(title, " ", name);
}
The generated Visual Basic source code for the second if statement:
If (name.Length > 0) Then
salutation = String.Concat(title, " ", name)
End If
Sample code for generating the method return statement:
test.Statements.Add(new CodeMethodReturnStatement(
new CodeVariableReferenceExpression("salutation")));
The generated CSharp source code for the return statement:
The generated Visual Basic source code for the return statement:
We have now completed the method body and the generated source code for the complete
method is as follows.
The generated CSharp source code for the example method:
public string CreateSalutation(string title) {
if ((title == null)) {
throw new System.ArgumentNullException("title");
}
string salutation = string.Empty;
string name = this.CreateFullName();
if ((name.Length > 0)) {
salutation = string.Concat(title, " ", name);
}
return salutation;
}
The generated Visual Basic source code for the example
method:
Public Function CreateSalutation(ByVal title As String) As String
If (title Is Nothing) Then
Throw New System.ArgumentNullException("title")
End If
Dim salutation As String = String.Empty
Dim name As String = Me.CreateFullName
If (name.Length > 0) Then
salutation = String.Concat(title, " ", name)
End If
Return salutation
End Function
We won't show the code required to generate the method source code in full, as it
may just put you off writing a code generator! However, if you review the code required
to generate each of the individual statements you will appreciate that developing
a code generator is not a trivial task and source code generation has to deliver
sufficient benefits to make the effort worthwhile. Fortunately, if you pick the
right sort of class or classes to generate, the time saving and code consistency
provided by source code generation more than repay the initial development effort.
Generating Code and Documentation Comments
Documentation comments can be added to any CodeTypeMember derived class using the
Comments property which references a collection of CodeCommentStatement instances.
Some of the other System.CodeDom classes also implement a Comments property that
may or may not work in the same way as the CodeTypeMember Comments property. For
example, interestingly, the CodeNamespace class has a Comments property, however,
you should not add documentation comments to a CodeNamespace because most language
compilers will not generate documentation for a namespace. You should only add standard
code comments to a CodeNamespace instance using its Comments property, if you add
documentation comments, most code providers that support documentation comments
seem happy to output namespace documentation comments but they won't be processed
by the relevant compiler (for now?).
Comments, standard or documentation, that you want to appear above a type or member
should be added using the member's, inherited, Comments property. Standard code
comments that you want to appear in a member's code body should be added to the
member's CodeStatementCollection. For example, to add code comments to a property
getter, use the GetStatements property, to add code comments to a method use the
Statements property.
Code comments are created using an instance of the CodeComment class, the DocComment
property should be set to true, either explicitly or during construction, to specify
that the comment is a documentation comment, by default the DocComment property
is set to false. A CodeComment instance cannot be directly assigned to a code object,
it has to be assigned to a CodeCommentStatement class instance before assigning
the CodeCommentStatement instance to the code object. You can create an instance
of the CodeCommentStatement class directly, to short-cut the creation process or
you can first create the CodeComment instance and then use it to construct an instance
of the CodeCommentStatement class. The CodeCommentStatement class represents a single
line comment, to create multi-line comments you have to use multiple instances of
the CodeCommentStatement class, one instance per line.
Sample code for creating CodeCommentStatement instances:
// Standard code comments.
CodeComment comment = new CodeComment("TODO:");
method.Statements.Add(new CodeCommentStatement(comment));
method.Statements.Add(new CodeCommentStatement("Add method implementation."));
Sample code for creating standard and documentation comments:
CodeCompileUnit graph = new CodeCompileUnit();
CodeNamespace samples = new CodeNamespace("Samples");
CodeComment comment = new CodeComment("CodeDom samples.");
samples.Comments.Add(new CodeCommentStatement(comment));
samples.Imports.Add(new CodeNamespaceImport("System"));
graph.Namespaces.Add(samples);
CodeTypeDeclaration sampleClass = new CodeTypeDeclaration("TestClass");
sampleClass.Comments.Add(
new CodeCommentStatement("<summary>", true));
sampleClass.Comments.Add(
new CodeCommentStatement("Type documentation summary comment.",
true));
sampleClass.Comments.Add(
new CodeCommentStatement("</summary>", true));
samples.Types.Add(sampleClass);
CodeMemberMethod test = new CodeMemberMethod();
test.Name = "TestMethod";
test.Parameters.Add(
new CodeParameterDeclarationExpression(typeof(int),
"id"));
test.ReturnType = new CodeTypeReference("System.Void");
test.Attributes =
(test.Attributes & ~MemberAttributes.AccessMask) |
MemberAttributes.Public;
test.Comments.Add(
new CodeCommentStatement("<summary>", true));
test.Comments.Add(
new CodeCommentStatement("Type member documentation summary comment.",
true));
test.Comments.Add(
new CodeCommentStatement("</summary>", true));
test.Statements.Add(
new CodeCommentStatement("TODO: Add method implementation.",
false));
sampleClass.Members.Add(test);
The generated CSharp source code for the standard and documentation comments example:
using System;
// CodeDom samples.
namespace Samples {
/// <summary>
/// Type documentation summary comment.
/// </summary>
public class TestClass {
/// <summary>
/// Type member documentation summary comment.
/// </summary>
public void TestMethod(int id) {
// TODO: Add method implementation.
}
}
}
The generated Visual Basic source code for the standard and documentation comments
example:
Imports System
'''<summary>
'''Type documentation summary comment.
'''</summary>
Public Class TestClass
'''<summary>
'''Type member documentation summary comment.
'''</summary>
Public Sub TestMethod(ByVal id As Integer)
'TODO: Add method implementation.
End Sub
End Class
Generating the Source Code
In the preceding sections we have discussed code providers and demonstrated how
to use the types in the System.CodeDom namespace to create a Code Document Object
Model and we have shown the source code generated from the examples. To keep the
examples short and to the point we have not, yet, demonstrated how to use a code
provider to generate the source code. For all of the examples in this article we
used the following, helper, methods to generate the source code.
Using a code provider to generate .Net source code:
private CodeDomProvider GetProvider(string language)
{
return CodeDomProvider.CreateProvider(language);
}
private void Generate(CodeCompileUnit graph, string language)
{
CodeDomProvider provider = GetProvider(language);
string outputFile = string.Concat(OUTPUT_FILE, ".",
provider.FileExtension);
CodeGeneratorOptions options = new CodeGeneratorOptions();
using (IndentedTextWriter writer = new IndentedTextWriter(
new StreamWriter(outputFile, false), options.IndentString))
{
// WARNING. Will overwrite an existing file without warning...
provider.GenerateCodeFromCompileUnit(graph, writer, options);
writer.Close();
}
}
The example code uses the code provider's static CreateProvider method to create
an instance of a CodeProvider derived language-specific code provider. The source
code output file path is built using a constant to specify the folder path and file
base name and the provider's FileExtension property is used to append the
language-specific source code file extension e.g. "cs", "vb", etc. to
the file base name. In a "real" code generator application the user would, typically,
specify the path and name of the source code file but you should still use the
FileExtension property to append the relevant file extension.
For the examples demonstrated in this article we have use the default generation
options as defined in an instance of the System.CodeDom.Compiler.CodeGeneratorOptions
class. You can use the CodeGeneratorOptions class to specify how the source code
should be formatted, for example, you can specify bracing using the BracingStyle
property, the code indent using the IndentString property and the output order using
the VerbatimOrder property. If you specify formatting options you should note that
some development environments allow a developer to specify their own, preferred,
formatting options. For example, Visual Studio allows a developer to specify bracing
style and the environment setting will override the formatting specified when generating
the source code.
The System.CodeDom.Compiler.IndentedTextWriter class is used to write the source
code out to file. This is good choice for source code output but any System.IO.TextWriter
derived class can be used.
Finally, the code provider's GenerateCodeFromCompileUnit method is used to generate
the source code from the source code object model and write the code out to a file
using the specified generator options.
The CodeDomProvider base class defines several methods that can be overridden by
a language-specific code provider to generate source code, you don't have to use
a CodeCompileUnit. For example, you can use the GenerateCodeFromMember method to
generate source code from a CodeTypeMember derived class or the GenerateCodeFromStatement
method to generate source code from a CodeStatement derived class.
Conclusion
We have covered a lot of ground in this article but inevitably, we have had to miss
out quite a lot as well, we do, however, hope that we have whetted your appetite
for source code generation and that you will start developing your own code generators.
Is source code generation worth the effort? We think that it most definitely is.
Pick your subjects carefully and concentrate on automating the generation of boilerplate
code i.e. code that you and your team use repeatedly, code that is similar in each
implementation but not identical and you will reap the rewards i.e. save development
and testing time and produce more consistent code.
Copyright ©Blayd Software Limited 2009-2010. All rights reserved.