Code Generators with .NET

 

Pierre Couzy
WinWise

November 3, 2004

Summary: Code generators are part of your everyday life, even if you don't realize it. Pierre Couzy shows you how you can leverage them in your projects. (8 printed pages)

Introduction

Imagine you're working in a company where DBAs rule the world: You can't build an application that just "goes to Oracle to fetch some records." You have to rely only on stored procedures, because there is a security layer at that level.

Building your app typically involves the following steps:

  1. Create a bunch of stored procedures.
  2. Create C# classes that communicate with the stored procs.
  3. Create higher-level classes that will manage either Microsoft ASP.NET forms or Microsoft Windows Forms, or form another tier.

1 and 2 are closely related: They share a lot of structure, they consume what the other produces, and so on. The problem is that you've got 2 different languages (PL-SQL and C# for this example), and can't reference one from the other.

Not a big deal you say? The first developer on the team will get everything right, because he knows the intrinsics of .NET, PL/SQL, and also the security model. He will probably write two things: First a set of helper classes, then a working example, and voilà—other developers will use the helper classes, copy/paste the working sample, and adapt to their needs.

If you've been long enough in the software industry, you know what will happen next: As requirements evolve, the helper classes will slowly grow and some of them will soon be obsolete. Of course, no one will dare modify them, because it might break older projects. At the same time, developers will re-use a working sample that's no longer in sync with the new rules, and they will have no way to understand what parts are still used and what parts can be thrown away without risk. You'll end up with a complete mess (copy/paste programming).

There are many ways to cope with this complexity, and I'll introduce a useful one that you may have neglected—code generators. You already use them, even if you're not aware of it: Each time you create a new ASPX page, a hidden mechanism converts it into a C# or Microsoft Visual Basic class. The same thing happens when you reference a COM component, a Web service, or even a data structure (typed dataset) in your projects—a class gets auto-generated, which hides the complexity you don't want to know about.

Tools and Examples

The first code generators you'll encounter are just tools that hide complexity. They don't allow you to explain how to generate the code, and they don't let you change the generated code afterwards (they will erase your updates).

The Add Web Reference Wizard, for example, does not even show you the code inserted into your project unless you ask for it.

Figure 1. Showing code generated for a Web reference

The Windows Forms designer is also a code generator. Here you see the code, but you'd better not touch it.

      #region Windows Form Designer generated code
      /// <summary>
      /// Required method for Designer support - do not modify
      /// the contents of this method with the code editor.
      /// </summary>
      private void InitializeComponent()
      {
         this.button1 = new System.Windows.Forms.Button();
         this.SuspendLayout();

Let's go a bit further: Code generators can provide you with a well-formed skeleton where you will later add your code. You probably know Reflector, but do you know that some add-ins (http://www.dotnetwiki.org/Default.aspx?tabid=52) to Reflector include code generators? They allow you to do the following:

Figure 2. Using Reflector to generate code

When you press the Generate button, a new file is created:

// Generated by Refly
namespace MyTestNameSpace
{
    using System;
    
    
    /// <summary>Test fixture for the &lt;see cref="SomeBusinessClass"/&gt; class
    ///</summary>
    /// <remarks />
    [TestFixture()]
    public class SomeBusinessClassTest
    {
        
        /// <summary />
        /// <remarks />
        private SomeBusinessClass _someBusinessClass = null;
        
        /// <summary />
        /// <remarks />
        public virtual SomeBusinessClass SomeBusinessClass
        {
            get
            {
                return this._someBusinessClass;
            }
        }
        
        /// <summary>Tests the EstimateSomeNumber method</summary>
        /// <remarks>
        ///   <para>Test coverage (estimated): 100,0%</para>
        ///   <para>Target path:</para>
        ///   <code>/* 0 */ return 0x2a;
        /// </code>
        /// </remarks>
        [Test()]
        [Ignore()]
        public virtual void EstimateSomeNumber0()
        {
            throw new System.NotImplementedException();
        }
        
        /// <summary>Tests the IsSomethingAlreadyInDataBase method</summary>
        /// <remarks>
        ///   <para>Test coverage (estimated): 100,0%</para>
        ///   <para>Target path:</para>
        ///   <code>/* 0 */ return false;
        /// </code>
        /// </remarks>
        [Test()]
        [Ignore()]
        public virtual void IsSomethingAlreadyInDataBase0()
        {
            throw new System.NotImplementedException();
        }
        
        /// <summary>Sets up the fixture</summary>
        /// <remarks />
        [SetUp()]
        public virtual void SetUp()
        {
            throw new System.NotImplementedException();
        }
        
        /// <summary>Releases resource allocated in the fixture</summary>
        /// <remarks />
        [TearDown()]
        public virtual void TearDown()
        {
            throw new System.NotImplementedException();
        }
    }
}

You didn't know a thing about Unit Testing, and yet you're able to generate a working test class. The difference from our previous example is that this time you have to code inside the generated class. We still don't have the ability to create our own templates though.

The next step is to create our own code generators. All we need is a way to generate text files (C#, PL-SQL, and so on) from a common set of information (for instance, the name of the columns you'll be getting, the way you want to request information, the kind of transactions you're allowing, and so on). Of course, you can't generate everything, so you need a tool that will build a customized skeleton, and a developer will later add specific implementation details.

That tool could be Perl, Microsoft VBScript, or for that matter plain old ASP—after all, it's a good tool to generate a text file from parameters. You probably used ASP to produce HTML files, but it was also good for generating Microsoft Excel or CSV, WML—so why not T-SQL or C#?

Here is what your ASP file could look like:

<%@ Language=VBScript %>
<%if request("Generate").count = 0 then%>
<HTML><BODY>
   <form method=post>
   <P>Property name :<INPUT name=PropertyName></P>
   <P>What is the type of your property ? <INPUT name=PropertyType></P>
   <P>Read only <input type=checkbox name=ReadOnly value=true></P>
   <P><INPUT type=submit value="Generate !" name=Generate></P>
   </form>
</BODY></HTML>
<% else 
dim PropertyName, PropertyType, ReadOnly, PropertyModifier
PropertyName = Request("PropertyName")
PropertyType = Request("PropertyType")
ReadOnly = (Request("ReadOnly")="true")
if ReadOnly then PropertyModifier = "ReadOnly" else PropertyModifier = ""
Response.ContentType = "text/plain"
%>

Private _<%=PropertyName%> as <%=PropertyType%>

Public <%=propertyModifier%> Property <%=PropertyName%> as <%=PropertyType%>

   Get
      Return _<%=PropertyName%>
   End Get
<%if not ReadOnly then%>
   Set (ByVal Value as <%=PropertyType%>)
      _<%=PropertyName%> = Value
   End Set
<%end if%>
End Property
<%end if%>

When you execute this file, you'll get something like this:

Figure 3. Generation Web page

Figure 4. Generated code

You'll also find code generators on the Internet. I generally use CodeSmith; it's free and simple to understand, but you'll find many others. Figure 5 is a screenshot showing a clever way to wait for generics; it generates a strongly-typed hashtable.

Click here for larger image.

Figure 5. CodeSmith

Those tools usually take 2 inputs—a template file (like our ASP file) and a XML parameter file (like the content of the form)—and produce a new file.

Developers are given the templates, and they typically use 2 different templates with the same set of parameters. This way they give the same information to the 2 templates and get one T-SQL file and one C# file sharing information—no risk of forgetting or mis-typing; the tedious part of the work gets done for you.

Versioning

Of course, things get complicated when you use templates and auto-generated code. In real life the templates evolve (for example, you may want to give the C# class an asynchronous way to call some stored procs), and when they do, you will need a way to cope with the implementation put in by the developer after the skeleton was generated.

In order to do this, you'll have to ensure that code added by a developer is independent of code generated by your template. The simplest technique to accomplish this is to create two different classes—one for your generated code, and a second that inherits from the first but will not be generated.

Figure 6. Versioning generated code

This technique is fine when you generate .NET code, because of the inheritance mechanism, but if you want to generate other kinds of code (VBScript, SQL) you won't be able to work this way. In that case you're pretty much left on your own. The mechanism I use is simple: I provide placeholders in the generated code (regions in C# and blocks enclosed by specific comments in SQL) and only code in those regions is kept when the the code is regenerated. Transferring the customized code itself is equally simple: The templates always accept a PreviousFile parameter. When this parameter is present, the previous file is parsed to get the customized code and re-inject it into the current generated file.

Never mess with generated code with no way to round-trip: you'll find yourself doing copy/paste programming.

Conclusion

Here are some things to keep in mind when working with code generators:

  • If your generated code is not meant to be modified, state this up front.
  • Don't forget that developers won't have time to understand the innards of the generated code, so help them and document the entry points as much as possible.
  • Think about versioning, and be explicit about what you will let people do. Maybe you'll want developers to add custom code only in specific regions of the code, or maybe you'll want them to inherit from your classes.
  • A new version of a template should always be able to regenerate what an old version did. If the new version needs more code than the old one did in order to work properly, make the places where code is needed obvious, and insert a bit of code that generates an error (at compile-time when possible).
  • If you decide to break the versioning mechanism of a template (for example by adding code that can't be round-tripped when applying a new version), always keep a copy of the template and of the parameters you used. Bad things will happen to you if you need to generate another similar code file for an old project and you've lost the template's version you used at that time. I simply put templates and parameters in SourceSafe along with the project.
  • Thoroughly test your templates before releasing them. Templates spread awfully fast among developers, because they solve problems without requiring much knowledge. Bugs will be hard to find, and harder still to fix if updating a generated file means losing any customizations.

How far can you go with this approach? Simply put, code generators allow the developer to gain one level of abstraction. He doesn't have to focus on the technical process, because it's embedded in templates, and can think about integrating the processes. It allows better understanding of customer needs, and better quality of technical processes. Microsoft Visual Studio already makes an extensive use of these techniques, and the upcoming version adds many improvements (notably a class modeller and partial classes mechanism to separate generated code from custom code).

 

.NET in the Real World

Pierre Couzy is a trainer and consultant specializing in the architecture of distributed systems. With more than 20 books to his credit, he's currently employed by WinWise as an .ASP.NET and BizTalk expert. He's been Regional Director since 2003, and is a speaker at many major conferences in France. Should you want to discuss French jazz, Bridge (yes, that old ladies' game), or even computing, you can reach him at Pierre.couzy@winwise.fr.