WuffProjects.CodeGeneration - CodeProject

:

Introduction

WuffProjects.CodeGeneration is a very basic code generation framework. It is, for now, meant to generate only C# code with.

It is now in beta state and can be downloaded. The package is to this article. This article is meant to show how it works and what is required. It is very important for me to collect feedback about what can be improved and if it's useful from your point of view.

Background

Today, many developers are confronted with great datamodels which result in a lot of model classes which are all basically looking the same. Therefore, often T4 templates are used to generate classes out of EntityFramework models. T4 templates are not very good supported by Microsoft and Visual Studio and many of them look like a big mess after some iterations of changes.

To go around that, WuffProjects.CodeGeneration was developed to be used inside your code generation project or T4 template to make your code generation code look clear and make it much better to read and also make your generated code look nice.

The Package

 

The package which I provided for download contains a few samples that show you how to use the code. Nevertheless, I want to give some samples in this article, to give you an idea of how it can be used and if it is the right thing for your software project.

You can also download the nuget package:

PM> Install-Package WuffProjects.CodeGeneration 

Using The Code

Now for some samples of how WuffProjects.CodeGeneration is used.

The code generation depends completely on lambdas so the result looks like a big code generation tree.

Basics

First of all, you need to instance a CodeFile.

var codeFile = new CodeFile();

The constructor of CodeFile takes a params Func<CodeFile, ICodeElement>[] named "content", so you can basically put everything you want to generate in here.

Let's see how to create a file with some usings, a namespace and a TestClass:

var codeFile = new CodeFile(
    file => file.Using("System"),
    file => file.Using("System.Collections.Generic"),
    file => file.EmptyLine(),
    file => file.Namespace("WuffProjects.TestCode",
        namespaceNode => namespaceNode.Public().Static().Class("TestClass")));

The CodeFile has only one important method: Render().

Calling codeFile.Render() results in the following generated code:

using System;
using System.Collections.Generic;

namespace WuffProjects.TestCode
{
    public static class TestClass
    {
    }
}
More Features

Now let's add a simple "Add" Function:

var codeFile = new CodeFile(
    file => file.Using("System"),
    file => file.Using("System.Collections.Generic"),
    file => file.EmptyLine(),
    file => file.Namespace("WuffProjects.TestCode",
        namespaceNode => namespaceNode.Public().Static().Class("TestClass",
            classNode => classNode.Function<int>("Add",
                functionNode => functionNode.Parameter<int>("a"),
                functionNode => functionNode.Parameter<int>("b"),
                functionNode => functionNode.Free("return a + b;")))));

The result looks like this:

using System;
using System.Collections.Generic;

namespace WuffProjects.TestCode
{
    public static class TestClass
    {
        Int32 Add(Int32 a, Int32 b)
        {
            return a + b;
        }
    }
}
Repeat lines of generated code

Normally, you want to repeat parts of the code. That can be done with the Repeat extension.

Let's create some classes from a List<string>:

var classNames = new List<string> { "ClassA", "ClassB", "ClassC" };

var codeFile = new CodeFile(
    file => file.Using("System"),
    file => file.Using("System.Collections.Generic"),
    file => file.EmptyLine(),
    file => file.Namespace("WuffProjects.TestCode",
        namespaceNode => namespaceNode.Repeat(classNames,
            (repeatNode, className) => repeatNode.Public().Class(className,
                classNode => classNode.Comment("Hello I am " + className)))));

As you can see, the parameter className can be used at any depth below the Repeat() extension call.

You can use Repeat() from any element and certainly below other repeats.

The result:

using System;
using System.Collections.Generic;

namespace WuffProjects.TestCode
{
    public class ClassA
    {
        // Hello I am ClassA
    }
    public class ClassB
    {
        // Hello I am ClassB
    }
    public class ClassC
    {
        // Hello I am ClassC
    }
}
Repeat more complex code

Let's now add some functions to a test class:

var functions = new[]
{
    new { Name = "SetA", ParameterName = "aInput", 
    Comments = new List<string> {"a", "b", "c"} },
    new { Name = "SetB", ParameterName = "bInput", 
    Comments = new List<string> {"d", "e", "f"} },
    new { Name = "SetC", ParameterName = "cInput", 
    Comments = new List<string> {"g", "h", "i"} }
};

var codeFile = new CodeFile
(
    file => file.Using("System"),
    file => file.EmptyLine(),
    file => file.Namespace("WuffProjects.TestCode",
        namespaceNode => namespaceNode.Public().Class("MyClass",
            classNode => classNode.Repeat(functions,
                (repeatFunctionsNode, function) => repeatFunctionsNode.Public().Function
							("void", function.Name,
                    functionNode => functionNode.Parameter<int>(function.ParameterName),
                    functionNode => functionNode.Repeat(function.Comments,
                        (repeatComments, comment) => repeatComments.Comment(comment)))))));

The previous code results in:

using System;

namespace WuffProjects.TestCode
{
    public class MyClass
    {
        public void SetA(Int32 aInput)
        {
            // a
            // b
            // c
        }
        public void SetB(Int32 bInput)
        {
            // d
            // e
            // f
        }
        public void SetC(Int32 cInput)
        {
            // g
            // h
            // i
        }
    }
}

Take a look at what we took to generate that code.

We had an anonymous object array containing objects with a Name, ParameterName and a List<string> of comments. You can see that the comments are added in the functions they were meant to.

With that power, you can create code from any kind of object model. You don't have to convert your information into anything. Just use them and generate your code!

"Get down the tree"

To get more structure into the code and not end up with a giant code tree which no one can maintain, you can separate parts of the code into other functions.

See how it works:

List<string> memberNames = new List<string> 
{ "FirstName", "LastName", "City", "Phone" };

var codeFile = new CodeFile
(
    file => file.Using("System"),
    file => file.EmptyLine(),
    file => file.Namespace("WuffProjects.TestCode",
        namespaceNode => namespaceNode.Public().Static().Class("TestClass",
            GetFunctions(memberNames).ToArray())));


private static IEnumerable<Func<Class, ICodeElement>> GetFunctions(List<string> memberNames)
{
    foreach (var memberName in memberNames)
        yield return parent => parent.Private().Static().Function<string>("Get" + memberName,
            functionNode => functionNode.Comment("Returns the value of the member m_" + memberName),
            functionNode => functionNode.Free("return m_" + memberName + ";"));
}

The result:

using System;

namespace WuffProjects.TestCode
{
    public static class TestClass
    {
        private static String GetFirstName()
        {
            // Returns the value of the member m_FirstName
            return m_FirstName;
        }
        private static String GetLastName()
        {
            // Returns the value of the member m_LastName
            return m_LastName;
        }
        private static String GetCity()
        {
            // Returns the value of the member m_City
            return m_City;
        }
        private static String GetPhone()
        {
            // Returns the value of the member m_Phone
            return m_Phone;
        }
    }
}

What's Amazing About That?

  • The framework provides a template for nearly all code structures you might want to generate.
  • You don't have to care about indentation, the result is always well formatted.
  • You don't have to care about any string operations, it's all done for you.
  • Types can be passed as type parameter so you don't have to write them as strings.
  • Hierarchical validation checking: You can only add a class or a function where it is allowed to exist.
  • The Free() extension allows you to add any free code snippet at any place of your file.
  • You can repeat code snippets and use your own datastructure as information source.

The Code Elements

The following code elements are currently implemented and can be created by one or more different extension methods:

Basic
  • Namespace
  • Using
  • Class
  • Struct
  • Interface
    • Property signature
    • Function signature
  • Enum
  • Property
  • Variable
  • Function
  • Constructor
  • Attribute
  • Event
  • Delegate
  • Extends (class)
  • Implements (Implementation of interfaces for classes, structs and interfaces)
Modifier
  • Access modifier
    • Public
    • Private
    • Internal
    • Protected
  • Modifier
    • Static, abstract, const, sealed, partial, override, new, ……
Operations
  • Assign
  • Increment
  • Decrement
Statements
  • Iteration statements
  • Jump statements
  • Selection statements
Organizer
  • Comment
  • Empty line
  • Nothing (That really renders to nothing)
  • Free (Any free code snippet you like to add)
  • Repeat (Repeates the inside code for a given enumerable)

History

  • 2015-04-03: Added the article
  • 2015-04-07: Added latest version of development package
  • 2015-04-09: Added section "The code elements"
  • 2015-04-21: Added new code elements which where released in version 1.2.0