C# Code Generation

Code generation is a big part of most modern frameworks. .NET is full of code generators that you’ve probably used – maybe without even knowing it. XAML files are converted to .g.cs files that are then fed to the compiler at compilation time. WCF is bundled with a code generator that will convert a WSDL file to a client to easily connect to existing web services. Today’s tutorial will cover how to use objects provided by the .NET framework to build your own code generator.

The code we’re going to generate today won’t be especially useful, however it will demonstrate a lot of the available functionality. I’m going to use C# to generate another C# class that can hold part of someone’s Twitter feed.

The .NET namespace that makes all this possible is System.CodeDom. Much like the name implies, these classes allow you to build a “DOM” of class hierarchies and then dump them out to text.

The first thing you should check out before starting is a Twitter feed – I would suggest Switch On The Code’s. There’s a lot of available elements in a feed, however I’m only really interested in a couple for this tutorial – date, text, and source.

All right, let’s start generating some code. Like any other C# class, we’re going to need to start with a namespace.

using System.CodeDom;
using Microsoft.CSharp;
using System.IO;

namespace CodeGeneration
{
  class Program
  {
    static void Main(string[] args)
    {
      CodeCompileUnit compileUnit = new CodeCompileUnit();

      // Add a namespace.
      CodeNamespace twitterNamespace = new CodeNamespace("TwitterClient");
      compileUnit.Namespaces.Add(twitterNamespace);
 
      // Write the code to a file.
      using (var fileStream = new StreamWriter(File.Create(@"C:\outputfile.cs")))
      {
        var provider = new CSharpCodeProvider();
        provider.GenerateCodeFromCompileUnit(compileUnit, fileStream, null);
      }
    }
  }
}

The root of every code DOM is the CodeCompileUnit. Inside it I create and add a CodeNamespace. Lastly, I use a CSharpCodeProvider to write the code I just generated to a file.

//——————————————————————————
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:4.0.30319.1
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//——————————————————————————

namespace TwitterClient {
   
}

Out of the box, there’s not much going on. It created some comments at the top and our namespace. We now need a class inside this namespace to hold a single Tweet.

// Add a Tweet class.
CodeTypeDeclaration twitterClass = new CodeTypeDeclaration("Tweet");
twitterClass.IsClass = true;
twitterClass.Attributes = MemberAttributes.Public;
twitterNamespace.Types.Add(twitterClass);

Our generated class is now up to this:

namespace TwitterClient {
   
   
    public class Tweet {
    }
}

A class isn’t very useful if it can’t hold anything, let’s add some fields to hold the date, text, and source of the tweet.

// Create a field to hold the date.
CodeMemberField dateField = new CodeMemberField(typeof(DateTime), "_date");
dateField.Attributes = MemberAttributes.Private;
twitterClass.Members.Add(dateField);

// Create a field to hold the text.
CodeMemberField textField = new CodeMemberField(typeof(string), "_text");
dateField.Attributes = MemberAttributes.Private;
twitterClass.Members.Add(textField);

// Create a field to hold the source.
CodeMemberField sourceField = new CodeMemberField(typeof(string), "_source");
dateField.Attributes = MemberAttributes.Private;
twitterClass.Members.Add(sourceField);

We now need properties to access each of these fields. There’s a lot of syntax required to make a property, so I created a simple helper function to make this a little cleaner.

/// <summary>
/// Creates a public property with getters and setters that wrap the
/// specified field.
/// </summary>
/// <param name="field">The field to get and set.</param>
/// <param name="name">The name of the property.</param>
/// <param name="type">The type of the property.</param>
/// <returns></returns>
static CodeMemberProperty CreateProperty(string field, string name, Type type)
{
  CodeMemberProperty property = new CodeMemberProperty()
  {
    Name = name,
    Type = new CodeTypeReference(type),
    Attributes = MemberAttributes.Public
  };

  property.SetStatements.Add(
    new CodeAssignStatement(
      new CodeFieldReferenceExpression(null, field),
          new CodePropertySetValueReferenceExpression()));

  property.GetStatements.Add(
    new CodeMethodReturnStatement(
      new CodeFieldReferenceExpression(null, field)));

  return property;
}

Since implicit properties are syntactic sugar added to C#, they’re not supported by the CodeDom. This means we need to explicitly create getters and setters for our private fields we added earlier. The setter is created by using a CodeAssignStatement. This object takes two parameters – essentially the left and right sides of the assignment. The left side is the field we want to assign. I passed null as the first parameter to CodeFieldReferenceExpression because I didn’t want an object before the field name (object.fieldname) since the field exists within this class. The right side of the assignment is a special expression to represent the value keyword.

Let’s now call this function to add our properties.

// Add a property for date.
twitterClass.Members.Add(CreateProperty("_date", "Date", typeof(DateTime)));

// Add a property for text.
twitterClass.Members.Add(CreateProperty("_text", "Text", typeof(string)));

// Add a property for source.
twitterClass.Members.Add(CreateProperty("_source", "Source", typeof(string)));

If we combine all of the code and run it, our class now looks like this:

//——————————————————————————
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:4.0.30319.1
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//——————————————————————————

namespace TwitterClient {
   
   
    public class Tweet {
       
        private System.DateTime _date;
       
        private string _text;
       
        private string _source;
       
        public virtual System.DateTime Date {
            get {
                return _date;
            }
            set {
                _date = value;
            }
        }
       
        public virtual string Text {
            get {
                return _text;
            }
            set {
                _text = value;
            }
        }
       
        public virtual string Source {
            get {
                return _source;
            }
            set {
                _source = value;
            }
        }
    }
}

That’s it for a class that holds basic information about a tweet. You may notice the virtual keyword applied to each property. I can’t figure out how to remove this, however it won’t affect how the class is used. We now need some way to store a collection of these. Let’s create a class that extends a generic List of Tweet objects.

// Add a class to hold a collection of tweets.
twitterNamespace.Imports.Add(new CodeNamespaceImport("System.Collections.Generic"));
CodeTypeDeclaration twitterCollection = new CodeTypeDeclaration("Tweets");
twitterCollection.BaseTypes.Add(new CodeTypeReference("List",
  new CodeTypeReference("Tweet")));

twitterNamespace.Types.Add(twitterCollection);

In order to make this file compile, we first need to include an import to System.Collections.Generic. We then add a new type that extends List. The List takes an argument for it’s generic – in this case it’s the type for the generic – or our Tweet class. That’s it for our code, we can run this one last time and see our output.

//——————————————————————————
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:4.0.30319.1
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//——————————————————————————

namespace TwitterClient {
    using System.Collections.Generic;
   
   
    public class Tweet {
       
        private System.DateTime _date;
       
        private string _text;
       
        private string _source;
       
        public virtual System.DateTime Date {
            get {
                return _date;
            }
            set {
                _date = value;
            }
        }
       
        public virtual string Text {
            get {
                return _text;
            }
            set {
                _text = value;
            }
        }
       
        public virtual string Source {
            get {
                return _source;
            }
            set {
                _source = value;
            }
        }
    }
   
    public class Tweets : List<Tweet> {
    }
}

For completeness, here’s the entirety of the code required to generate the above example:

using System.CodeDom;
using Microsoft.CSharp;
using System.IO;
using System;

namespace CodeGeneration
{
  class Program
  {
    static void Main(string[] args)
    {
      CodeCompileUnit compileUnit = new CodeCompileUnit();

      // Add a namespace.
      CodeNamespace twitterNamespace = new CodeNamespace("TwitterClient");
      compileUnit.Namespaces.Add(twitterNamespace);

      // Add a Tweet class.
      CodeTypeDeclaration twitterClass = new CodeTypeDeclaration("Tweet");
      twitterClass.IsClass = true;
      twitterClass.Attributes = MemberAttributes.Public;
      twitterNamespace.Types.Add(twitterClass);

      // Create a field to hold the date.
      CodeMemberField dateField = new CodeMemberField(typeof(DateTime), "_date");
      dateField.Attributes = MemberAttributes.Private;
      twitterClass.Members.Add(dateField);

      // Create a field to hold the text.
      CodeMemberField textField = new CodeMemberField(typeof(string), "_text");
      dateField.Attributes = MemberAttributes.Private;
      twitterClass.Members.Add(textField);

      // Create a field to hold the source.
      CodeMemberField sourceField = new CodeMemberField(typeof(string), "_source");
      dateField.Attributes = MemberAttributes.Private;
      twitterClass.Members.Add(sourceField);

      // Add a property for date.
      twitterClass.Members.Add(CreateProperty("_date", "Date", typeof(DateTime)));

      // Add a property for text.
      twitterClass.Members.Add(CreateProperty("_text", "Text", typeof(string)));

      // Add a property for source.
      twitterClass.Members.Add(CreateProperty("_source", "Source", typeof(string)));

      // Add a class to hold a collection of tweets.
      twitterNamespace.Imports.Add(
        new CodeNamespaceImport("System.Collections.Generic"));
      CodeTypeDeclaration twitterCollection =
        new CodeTypeDeclaration("Tweets");
      twitterCollection.BaseTypes.Add(new CodeTypeReference("List",
        new CodeTypeReference("Tweet")));

      twitterNamespace.Types.Add(twitterCollection);
 
      // Write the code to a file.
      using (var fileStream = new StreamWriter(File.Create(@"C:\outputfile.cs")))
      {
        var provider = new CSharpCodeProvider();
        provider.GenerateCodeFromCompileUnit(compileUnit, fileStream, null);
      }
    }

    /// <summary>
    /// Creates a public property with getters and setters that wrap the
    /// specified field.
    /// </summary>
    /// <param name="field">The field to get and set.</param>
    /// <param name="name">The name of the property.</param>
    /// <param name="type">The type of the property.</param>
    /// <returns></returns>
    static CodeMemberProperty CreateProperty(string field, string name, Type type)
    {
      CodeMemberProperty property = new CodeMemberProperty()
      {
        Name = name,
        Type = new CodeTypeReference(type),
        Attributes = MemberAttributes.Public
      };

      property.SetStatements.Add(
        new CodeAssignStatement(
          new CodeFieldReferenceExpression(null, field),
              new CodePropertySetValueReferenceExpression()));

      property.GetStatements.Add(
        new CodeMethodReturnStatement(
          new CodeFieldReferenceExpression(null, field)));

      return property;
    }
  }
}

There you have it. This tutorial demonstrated how to generate classes, fields, properties, generics, and inheritance. The sky’s the limit on how you can put code generation to use for you and your projects. If you’ve got any questions or comments, please leave them below or check out the forums.

Leave a Reply

Your email address will not be published. Required fields are marked *