Wednesday, May 9, 2012

Programmatically Generating C# Enums

Have you ever wanted to automatically generate valid code from some input? I recently had exactly this problem. 

I had a simple set of data in a database that I wanted to access from within my code. For the sake of example, let's say I had a list of car makers in a database like this:
Primary Key
Car Maker Name
1
Acura
2
BMW
3
Chevrolet
4
Ford

Problem
In my code I wanted to have a strongly typed object to represent the manufacture of a car. In essence I wanted an enum named "CarMake" that had a value for every row in the table. This would let me have a statically typed value and I could enforce some guarantees on my code, from both the compiler and the application level (and thereby avoid having to deal with it at the database level). As far as I could see I had two options:

Options
  1. Create the Enum by hand
    1. This was simple, but I had a lot of makes (100+) and I would have to create and maintain this list by hand. If there were ever a  new car manufacturer, I'd have to update both my database as well as my enum code.
  2. Generate the Enum code automatically based on the values that were currently in the database

I chose option 2 because it seems more elegant (I'd rather spend time writing a tool to do work than mindlessly entering enums), and also I could easily update the enum values in the future by simply running the tool against the updated database table.

Requirements
The requirements of this enum generator were:
  • I wanted to be able to optionally specify the numeric value of the enum (in this case I wanted to make the value equal to the primary key index in a database)
  • I wanted to be able to optionally specify attributes for any given enum value
  • I wanted to be able to gracefully handle naming conflicts (e.g. trying to add "Acura" twice)

After doing a quick search on google, I found a couple of existing solutions that sort fulfilled these needs, but not completely, so I figured I'd write my own.

Using it
This is what I ended up with:

GenerateEnum ge = new GenerateEnum("CarModel", "SomeNamespace");

ge.AddEnumEntry("Acura");
ge.AddEnumEntry("BMW");
ge.AddEnumEntry("Chevrolet");
ge.AddEnumEntry("Ford");

ge.WriteToFile(@"C:\temp");

Output
And the output of this code is a file called "CarModel.cs" located at C:\temp containing:

namespace SomeNamespace
{
    public enum CarModel
    {
        Acura,
        BMW,
        Chevrolet,
        Ford
    }
}
Using this you can either generate an enum file to add to your code once, or hook it up to a pre-build event for your project so it gets updated every time you build.

But that's not all…

Adding Explicit Values
I needed a bit more flexibility so you can optionally provide the value you want the enum to take - in my case I wanted the value to map to the database entry's primary key:

Changing
ge.AddEnumEntry("Acura");
to
ge.AddEnumEntry("Acura", 123);

Generates:
namespace SomeNamespace
{
    public enum CarModel
    {
        Acura = 123,
        BMW,
        Chevrolet,
        Ford
    }
}

Adding Attributes
Let's take it one step further, say I want to add an attribute to each value stating whether that particular make is "foreign" or "domestic", here's how I would do it:

1) Create the type of attribute that want:
[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
public class MakeOrigin : Attribute
{
    readonly string origin;

    public MakeOrigin(string origin)
    {
        this.origin = origin;
    }

    public string Origin
    {
        get { return origin; }
    }

}

2) Create an instance of the EnumAttribute Class - this requires that you pass in both the type of the attribute you want to create, as well as the value to set it (in this case the string "Foreign").
Code is located here: https://github.com/danaltdel/GenerateEnums
var foreignEnum = new GenerateEnum.EnumAttribute(typeof(MakeOrigin), 
    new System.CodeDom.CodePrimitiveExpression("Foreign"));

3) Pass that into the AddEnumEntry method
ge.AddEnumEntry("Acura", 123, foreignEnum);
4) That's it - enjoy your result:
public enum CarModel
{
   [MakeOrigin("Foreign")]
   Acura = 123,
   BMW,
   Chevrolet,
   Ford,
}

Source Code is located here: http://github.com/danaltdel/GenerateEnums

No comments:

Post a Comment