Dogs Chasing Squirrels

A software development blog

Generating Code with Roslyn

6

Roslyn, now known as the .NET Compiler Platform, essentially provides a DOM for C# and Visual Basic. What it means for code generation is that you can construct an object representing your C# class and Roslyn will generate the .cs file.
This is the first in what I hope to be a series of posts showing how to generate particular elements. This will be for my own reference, if nothing else, but some may find it useful.

Preparing your Project

You’ll have to add the following references, probably using NuGet:

  • Microsoft.CodeAnalysis
  • Microsoft.CodeAnalysis.CSharp
  • Microsoft.CodeAnalysis.CSharp.Workspaces
  • Microsoft.CodeAnalysis.Workspaces
  • Microsoft.CSharp

Just search NuGet for “CodeAnalysis” and you’ll find them.

Generating Classes from Scratch

The first thing I tried to do with Roslyn was to create an empty class from scratch but I couldn’t find any documentation online for how to do it. Every example I saw started by parsing an existing .cs file and making changes to it. So here’s how you do it.
Any file is considered a Compilation Unit. You can declare one like this:

CompilationUnitSyntax cu = SF.CompilationUnit()

And what’s the first thing in a .cs file? The “using”s. Add them like this:

CompilationUnitSyntax cu = SF.CompilationUnit()
    .AddUsings( SF.UsingDirective( SF.IdentifierName( "System" ) ) )
    .AddUsings( SF.UsingDirective( SF.IdentifierName( "System.Generic" ) ) )
    ;

The most important thing to note here is that all Roslyn objects are immutable. So saying:

cu.AddUsings( SF.UsingDirective( SF.IdentifierName( "System" ) ) )

Isn’t going to get you anywhere. AddUsings doesn’t change the cu object; It returns a new CompilationUnitSyntax with the change. You would have to write:

cu = cu.AddUsings( SF.UsingDirective( SF.IdentifierName( "System" ) ) )

So in Roslyn examples you’ll often see people chaining all their code together. I avoid it because it becomes unreadable and unmaintainable.
Next we’ll add a namespace:

NamespaceDeclarationSyntax ns = SF.NamespaceDeclaration( SF.IdentifierName( "MyNamespace" ) );
cu = cu.AddMembers( ns );

Now we’ll add our type to the namespace. We’ll declare it as a partial, private class.

ClassDeclarationSyntax c = SF.ClassDeclaration( "MyClass" )
    .AddModifiers( SF.Token( SyntaxKind.PrivateKeyword ) )
    .AddModifiers( SF.Token( SyntaxKind.PartialKeyword ) )
    ;
ns = ns.AddMembers( c );

Finally, we’ll write our type out to a file using the SyntaxTree type. This will write to any TextWriter (like StreamWriter). Here I write it out to a StringBuilder. Note that I have to specify a filename anyway (“C:\out.cs”, below). It doesn’t get used.

SyntaxNode formattedNode = Formatter.Format( cu, new CustomWorkspace( "Host" ) );
StringBuilder sb = new StringBuilder();
using ( StringWriter writer = new StringWriter( sb ) ) {
    formattedNode.WriteTo( writer );
}

And here’s what our class looks like:

using System;
using System.Generic;

namespace MyNamespace
{
    private partial class MyClass
    {
    }
}

Amazing.
I’ll have to figure out how to do something about the opening braces being on new lines. I don’t like putting opening braces on new lines.

[Edit 2015-08-23: See this update for some changes to Roslyn object names since this was first posted]

6 thoughts on “Generating Code with Roslyn

      1. Carlos

        I’m having an error on:

        SyntaxNode formattedNode = Formatter.Format( cu, new CustomWorkspace( “Host” ) );

        It doesn’t find CustomWorkspace

        Help please.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: