Skip to main content

C# Code Generation [Beginner]


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. 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 specialexpression 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.
Source Files:

Comments

Popular posts from this blog

C# Snippet - Shuffling a Dictionary [Beginner]

Randomizing something can be a daunting task, especially with all the algorithms out there. However, sometimes you just need to shuffle things up, in a simple, yet effective manner. Today we are going to take a quick look at an easy and simple way to randomize a dictionary, which is most likely something that you may be using in a complex application. The tricky thing about ordering dictionaries is that...well they are not ordered to begin with. Typically they are a chaotic collection of key/value pairs. There is no first element or last element, just elements. This is why it is a little tricky to randomize them. Before we get started, we need to build a quick dictionary. For this tutorial, we will be doing an extremely simple string/int dictionary, but rest assured the steps we take can be used for any kind of dictionary you can come up with, no matter what object types you use. Dictionary < String , int > origin = new Dictionary < string , int >(); ...

C# WPF Printing Part 2 - Pagination [Intermediate]

About two weeks ago, we had a tutorial here at SOTC on the basics of printing in WPF . It covered the standard stuff, like popping the print dialog, and what you needed to do to print visuals (both created in XAML and on the fly). But really, that's barely scratching the surface - any decent printing system in pretty much any application needs to be able to do a lot more than that. So today, we are going to take one more baby step forward into the world of printing - we are going to take a look at pagination. The main class that we will need to do pagination is the DocumentPaginator . I mentioned this class very briefly in the previous tutorial, but only in the context of the printing methods on PrintDialog , PrintVisual (which we focused on last time) and PrintDocument (which we will be focusing on today). This PrintDocument function takes a DocumentPaginator to print - and this is why we need to create one. Unfortunately, making a DocumentPaginator is not as easy as...

C# WPF Tutorial - Implementing IScrollInfo [Advanced]

The ScrollViewer in WPF is pretty handy (and quite flexible) - especially when compared to what you had to work with in WinForms ( ScrollableControl ). 98% of the time, I can make the ScrollViewer do what I need it to for the given situation. Those other 2 percent, though, can get kind of hairy. Fortunately, WPF provides the IScrollInfo interface - which is what we will be talking about today. So what is IScrollInfo ? Well, it is a way to take over the logic behind scrolling, while still maintaining the look and feel of the standard ScrollViewer . Now, first off, why in the world would we want to do that? To answer that question, I'm going to take a an example from a tutorial that is over a year old now - Creating a Custom Panel Control . In that tutorial, we created our own custom WPF panel (that animated!). One of the issues with that panel though (and the WPF WrapPanel in general) is that you have to disable the horizontal scrollbar if you put the panel in a ScrollV...