Represent .NET Types in Language-Neutral Terms
Use the System.CodeDom namespace to generically represent the key members of the .NET type system.
by Andrew Troelsen
VSLive! Orlando, September 19, 2002
Note: Andrew Troelsen presented "Advanced C#: Building Programs That Dynamically Generate Other Programs" at C# Live! Orlando, Tuesday, September 17. This tip is from that session.
If you've taken the time to examine multiple managed languages, you might have noticed that the source code documents each have a similar (but not identical) form. Each language has a specific bit of syntax that allows you to define a class, members of a class, parameters of a member, and whatnot. The .NET base class library supplies an interesting namespace named System.CodeDom, which allows you to express such syntactic tokens in language-neutral terms. Formally speaking, the collection of code DOM types that describe a given source code file is termed an "object graph."
Once you have assembled a given object graph in memory, code DOM allows you to interact with a language-specific "code provider." Simply put, a code provider is a class that provides access to the ICodeGenerator interface. Using the members of this type, you can persist your object graph into the syntax of a specific .NET language. So, if you have a code DOM object graph that represents a class named SportsCar, you can dynamically persist this type in terms of C#, COBOL .NET, VB.NET, and so forth.
As you might already be aware, many command-line tools that ship with the .NET SDK already make use of code DOM technologies. For example, consider wsdl.exe. Using the /l flag, you can instruct the tool to persist your Web service proxy in a variety of different languages. By way of a friendly primer, the remainder of this discussion will examine select members of the System.CodeDom namespace and illustrate how to generically represent the key members of the .NET type system (classes, interfaces, structures, enumerations).
Representing .NET Class Types
To begin untangling the numerous possibilities of type representation, let's walk through some concrete examples. First, assume you wish to insert a number of classes into the Intertech namespace. The MyPublicClass type is a public type that derives directly from System.Object:
// This is a public class
public class MyPublicClass : object {
}
The code behind the definition is straightforward (assume a helper function named PopulateNamespace(), which populates a CodeNamespace type):
private static void PopulateNamespace(ref
CodeNamespace n)
{
// Insert a public class.
CodeTypeDeclaration c = new
CodeTypeDeclaration ("MyPublicClass");
c.IsClass = true;
c.BaseTypes.Add (typeof (System.Object) );
c.TypeAttributes = TypeAttributes.Public;
c.Comments.Add(new CodeCommentStatement(
"This is a public class"));
n.Types.Add(c);
}
As you can see, the process begins by creating a new CodeTypeDeclaration and assigning the IsClass property to true. You specify the base class of the type through the BaseTypes property; simply pass in the type information (or string name if you can't get the literal type information) of the intended parent class. Notice that we are using the TypeAttributes.Public enumeration to specify the visibility of the MyPublicClass entity (by default, types are assumed to be visible only within the current assembly).
Now assume we wish to create an internal sealed class named MySealedClass, an abstract base class named (of course) MyAbstractClass, and a corresponding derived class (MyDerivedClass):
// This is an internal, sealed class
sealed class MySealedClass : object {
}
// This is an abstract class
public abstract class MyAbstractClass : object {
}
// This is a derived class
public class MyDerivedClass :
Intertech.MyAbstractClass {
}
Given what you already know about the CodeTypeDeclaration type, the code used to generate these new classes should come as no big surprise:
// Insert an internal, sealed class into the
// Intertech namespace.
c = new CodeTypeDeclaration ("MySealedClass");
c.IsClass = true;
c.BaseTypes.Add (typeof (System.Object) );
c.TypeAttributes = TypeAttributes.NotPublic |
TypeAttributes.Sealed;
c.Comments.Add(new CodeCommentStatement(
"This is an internal, sealed class"));
n.Types.Add(c);
// Now insert a public abstract class.
c = new CodeTypeDeclaration ("MyAbstractClass");
c.IsClass = true;
c.BaseTypes.Add (typeof (System.Object) );
c.TypeAttributes = TypeAttributes.Public |
TypeAttributes.Abstract;
c.Comments.Add(new CodeCommentStatement(
"This is an abstract class"));
n.Types.Add(c);
// Finally, insert a public class that derives
// from abstract class.
c = new CodeTypeDeclaration ("MyDerivedClass");
c.IsClass = true;
c.BaseTypes.Add("Intertech.MyAbstractClass");
c.TypeAttributes = TypeAttributes.Public;
c.Comments.Add(new CodeCommentStatement(
"This is a derived class"));
n.Types.Add(c);
Representing Enums, Interfaces, and Structures
Now that you've seen the process of inserting various class definitions into a CodeNamespace type, the act of defining enumerations, interfaces, and structures is a natural extension of what you already understand. Assume we wish to insert these additional items into the current namespace:
// A simple interface
public interface IMyInterface {
}
// An interface with multiple base interfaces
public interface IMyDerivedInterface :
IMyInterface, System.IComparable {
}
// An emum
public enum MyEnum {
}
// My custom value type
public struct MyStruct {
}
Here is the update to the PopulateNamespace() helper function (note the various calls to ICodeGenerator.Supports() to account for limitations of certain .NET programming languages). Assume we have obtained a reference to this interface (called itfCG), which has been obtained from a given code provider:
// Insert an empty interface.
c = new CodeTypeDeclaration("IMyInterface");
c.IsInterface = true;
c.TypeAttributes = TypeAttributes.Interface |
TypeAttributes.Public;
c.Comments.Add(new CodeCommentStatement(
"A simple interface"));
n.Types.Add(c);
// Now make another interface which derives form
// multiple base interfaces.
if(itfCG.Supports(GeneratorSupport.MultipleInterfaceMembers))
{
c = new CodeTypeDeclaration(
"IMyDerivedInterface");
c.IsInterface = true;
c.TypeAttributes = TypeAttributes.Interface |
TypeAttributes.Public;
c.BaseTypes.Add("IMyInterface");
c.BaseTypes.Add(typeof(System.IComparable));
c.Comments.Add(new
CodeCommentStatement(
"An interface with multiple base
interfaces"));
n.Types.Add(c);
}
// Insert an empty enumeration.
if(itfCG.Supports(GeneratorSupport.DeclareEnums))
{
c = new CodeTypeDeclaration("MyEnum");
c.IsEnum = true;
c.TypeAttributes = TypeAttributes.Public;
c.Comments.Add(new CodeCommentStatement(
"An emum"));
n.Types.Add(c);
}
// Insert an empty structure.
if(itfCG.Supports(GeneratorSupport.DeclareValueTypes))
{
c = new CodeTypeDeclaration("MyStruct");
c.IsStruct = true;
c.TypeAttributes = TypeAttributes.Public;
c.Comments.Add(new CodeCommentStatement(
"My custom value type"));
n.Types.Add(c);
}
The first point of interest has to do with the process of defining a .NET interface type. Notice that we are required to explicitly specify TypeAttributes.Interface in addition to setting the IsInterface property to true. If you forget to do so, you will find that the type is defined as a class, not an interface! Second, notice that when you wish to build an interface that has multiple base interfaces, simply add each item into the internal base types collection using the CodeTypeDeclaration.BaseType property.
Cool! At this point you've been given a view into the world of code DOM technologies. Of course, I have not described the complete process of building a valid object graph (let alone obtaining a valid ICodeGenerator interface). Nevertheless, you should have some insight regarding the process of representing code in memory.
About the Author
Andrew Troelsen is the VP of Instruction at Intertech, an enterprise developer training firm. Andrew has authored books in the Apress Intertech Instructor Series, including the best-selling C# and the .NET Platform, Visual Basic .NET and the .NET Platform: An Advanced Guide, and COM and .NET Interoperability. With Intertech, he teaches organizations such as NASA, the Mayo Clinic, and Microsoft.
|