Dogs Chasing Squirrels

A software development blog

Tag Archives: Roslyn

Roslyn Update

0

There have been some changes to Roslyn since my last post and some of my old examples don’t compile.

CustomWorkspace has been replaced by AdHocWorkspace

Creating a class from scratch is now:

AdhocWorkspace cw = new AdhocWorkspace();
OptionSet options = cw.Options;
options = options.WithChangedOption( CSharpFormattingOptions.NewLinesForBracesInMethods, false );
options = options.WithChangedOption( CSharpFormattingOptions.NewLinesForBracesInTypes, false );
SyntaxNode formattedNode = Formatter.Format( cu, cw, options );
StringBuilder sb = new StringBuilder();
using ( StringWriter writer = new StringWriter( sb ) ) {
    formattedNode.WriteTo( writer );
}

Note that some of the CSharpFormattingOptions syntax changed, too.
In our simple getter and setter, the BinaryExpression used for assigment is now an AssignmentExpression.

PropertyDeclarationSyntax property =
    SF.PropertyDeclaration( SF.ParseTypeName( "String" ), SF.Identifier( "A" ) )
        .AddModifiers( SF.Token( SyntaxKind.PublicKeyword ) )
        .AddAccessorListAccessors( 
            SF.AccessorDeclaration(
                SyntaxKind.GetAccessorDeclaration,
                SF.Block(
                    SF.List( new [] {
                        SF.ReturnStatement( SF.IdentifierName( "_a" ) )
                    } )
                )
            ),
            SF.AccessorDeclaration(
                SyntaxKind.SetAccessorDeclaration,
                SF.Block( 
                    SF.List( new [] {
                        SF.ExpressionStatement( 
                            SF.AssignmentExpression( 
                                SyntaxKind.SimpleAssignmentExpression,
                                SF.IdentifierName( "_a" ),
                                SF.IdentifierName( "value" )
                            )
                        )
                    } )
                )
            )
        )
    ;

Everything else from my old example project seems to compile.

Code Generation with Roslyn – If, Else, and Loops

1

if statements

This:

MethodDeclarationSyntax method = SF.MethodDeclaration(
    SF.PredefinedType( SF.Token( SyntaxKind.VoidKeyword ) ),
    "MyMethod"
    )
    .AddModifiers( SF.Token( SyntaxKind.PublicKeyword ) )
    .AddBodyStatements( 
        SF.IfStatement(
            SF.BinaryExpression( 
                SyntaxKind.EqualsExpression,
                SF.IdentifierName( "_a" ),
                SF.LiteralExpression( SyntaxKind.NumericLiteralExpression, SF.Literal( "X" ) )
            ),
            SF.Block(  
            )
        )
    )
    ;

Will get you this:

public void MyMethod() {
    if (_a == "X") {
    }
}

if-else statements

This:

MethodDeclarationSyntax method = SF.MethodDeclaration(
    SF.PredefinedType( SF.Token( SyntaxKind.VoidKeyword ) ),
    "MyMethod"
    )
    .AddModifiers( SF.Token( SyntaxKind.PublicKeyword ) )
    .AddBodyStatements( 
        SF.IfStatement(
            SF.BinaryExpression( 
                SyntaxKind.EqualsExpression,
                SF.IdentifierName( "_a" ),
                SF.LiteralExpression( SyntaxKind.NumericLiteralExpression, SF.Literal( "X" ) )
            ),
            SF.Block(  
            ),
            SF.ElseClause( 
                SF.Token( SyntaxKind.ElseKeyword ),
                SF.IfStatement( 
                    SF.BinaryExpression( 
                        SyntaxKind.EqualsExpression,
                        SF.IdentifierName( "_a" ),
                        SF.LiteralExpression( SyntaxKind.NumericLiteralExpression, SF.Literal( "Y" ) )
                    ),
                    SF.Block(  
                    ),
                    SF.ElseClause(
                        SF.Token( SyntaxKind.ElseKeyword ),
                        SF.Block( 
                        )
                    )
                )
            )
        )
    )

Will get you this:

public void MyMethod() {
    if (_a == "X") {
    }
    else if (_a == "Y") {
    }
    else {
    }
}

for loops

This:

MethodDeclarationSyntax method = SF.MethodDeclaration(
    SF.PredefinedType( SF.Token( SyntaxKind.VoidKeyword ) ),
    "MyMethod"
    )
    .AddModifiers( SF.Token( SyntaxKind.PublicKeyword ) )
    .AddBodyStatements( 
        SF.ForStatement(
            SF.VariableDeclaration( 
                SF.PredefinedType( SF.Token( SyntaxKind.IntKeyword ) ), 
                SF.SeparatedList(new [] {
                    SF.VariableDeclarator(
                        SF.Identifier( "i" ),
                        null,
                        SF.EqualsValueClause( SF.LiteralExpression( SyntaxKind.NumericLiteralExpression, SF.Literal( 0 ) ) ) 
                    )
                } )
            ),
            SF.SeparatedList<ExpressionSyntax>(),
            SF.BinaryExpression( 
                SyntaxKind.LessThanExpression,
                SF.IdentifierName( "i" ),
                SF.LiteralExpression( SyntaxKind.NumericLiteralExpression, SF.Literal( 10 ) )
            ),
            SF.SeparatedList<ExpressionSyntax>( new [] {
                SF.PostfixUnaryExpression(
                    SyntaxKind.PostIncrementExpression,
                    SF.IdentifierName( "i" )
                )
            } ),
            SF.Block(  
            )
        )
    )

Will get you this:

public void MyMethod() {
    for (int i = 0; i < 10; i++) {
    }
}

foreach loops

This defines our generic list:

LocalDeclarationStatementSyntax listDeclaration = SF.LocalDeclarationStatement(
    SF.TokenList(),
    SF.VariableDeclaration( 
        SF.GenericName( 
            SF.Identifier( "IList" ), 
            SF.TypeArgumentList( 
                SF.SeparatedList<TypeSyntax>( new [] { SF.PredefinedType( SF.Token( SyntaxKind.StringKeyword ) ) } ) 
            ) 
        ), 
        SF.SeparatedList( new [] {
            SF.VariableDeclarator(
                SF.Identifier( "list" ),
                null,
                SF.EqualsValueClause( 
                    SF.ObjectCreationExpression(
                        SF.Token( SyntaxKind.NewKeyword ),
                        SF.GenericName( 
                            SF.Identifier( "List" ), 
                            SF.TypeArgumentList( 
                                SF.SeparatedList<TypeSyntax>( new [] { SF.PredefinedType( SF.Token( SyntaxKind.StringKeyword ) ) } ) 
                            ) 
                        ),
                        SF.ArgumentList( SF.SeparatedList<ArgumentSyntax>( new ArgumentSyntax[0] ) ),
                        null
                    )
                )
            )
        } )
    )
);

And this our “foreach”:

ForEachStatementSyntax forEachStatement = SF.ForEachStatement(
    SF.PredefinedType( SF.Token( SyntaxKind.StringKeyword ) ),
    SF.Identifier( "item" ),
    SF.IdentifierName( "list" ),
    SF.Block()
);

Adding both to a method:

MethodDeclarationSyntax method = SF.MethodDeclaration(
    SF.PredefinedType( SF.Token( SyntaxKind.VoidKeyword ) ),
    "MyMethod"
    )
    .AddModifiers( SF.Token( SyntaxKind.PublicKeyword ) )
    .AddBodyStatements( 
        listDeclaration,
        forEachStatement
    );

Will get you this:

public void MyMethod() {
    IList<string> list = new List<string>();
    foreach (string item in list) {
    }
}

while loops

This is one of the more straightforward ones.
This:

MethodDeclarationSyntax method = SF.MethodDeclaration(
    SF.PredefinedType( SF.Token( SyntaxKind.VoidKeyword ) ),
    "MyMethod"
    )
    .AddModifiers( SF.Token( SyntaxKind.PublicKeyword ) )
    .AddBodyStatements( 
        SF.WhileStatement( 
            SF.LiteralExpression( SyntaxKind.TrueLiteralExpression ),
            SF.Block(  
                SF.BreakStatement()
            )
        )
    );

Will get you this:

public void MyMethod() {
    while (true) {
        break;
    }
}

Code Generation with Roslyn – Fields and Properties

0

I’m going to give a few more basic examples of code generation with Roslyn. The following assume that you have a ClassGenerationSyntax variable named “@class”. The examples have also shortened SyntaxFactory to SF using:

using SF = Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

Properties

The code

// Add a property
PropertyDeclarationSyntax @property = SF.PropertyDeclaration( SF.ParseTypeName( "String" ), "MyProperty"  )
    .AddModifiers( SF.Token( SyntaxKind.PublicKeyword ) );
// Add a getter
@property = @property.AddAccessorListAccessors(
    SF.AccessorDeclaration( SyntaxKind.GetAccessorDeclaration )
        .WithSemicolonToken( SF.Token(SyntaxKind.SemicolonToken ) 
        ) );
// Add a private setter
@property = @property.AddAccessorListAccessors( 
    SF.AccessorDeclaration( SyntaxKind.SetAccessorDeclaration )
    .AddModifiers( SF.Token( SyntaxKind.PrivateKeyword ) )
    .WithSemicolonToken( SF.Token(SyntaxKind.SemicolonToken ) 
    ) );
// Add the property to the class
@class = @class.AddMembers( @property );

Produces the code:

public class MyClass {
    public String MyProperty { get; private set; }
}

Variables

This adds a simple field to the class:

FieldDeclarationSyntax aField = SF.FieldDeclaration( 
    SF.VariableDeclaration( 
        SF.ParseTypeName( "String" ), 
        SF.SeparatedList(new [] { SF.VariableDeclarator( SF.Identifier( "_a" ) ) } )
    ) )
    .AddModifiers( SF.Token( SyntaxKind.PrivateKeyword ) );
@class = @class.AddMembers( aField );

Generating the code:

private String _a;

This code initializes the field to a new object:

ExpressionSyntax initializationExpression = SF.ObjectCreationExpression(
    SF.Token( SyntaxKind.NewKeyword ),
    SF.ParseTypeName( "OtherClass" ),
    SF.ArgumentList( SF.SeparatedList<ArgumentSyntax>( new [] {
        SF.Argument( SF.LiteralExpression( SyntaxKind.NumericLiteralExpression, SF.Literal( 1 ) ) ),
        SF.Argument( SF.LiteralExpression( SyntaxKind.StringLiteralExpression, SF.Literal( "abc" ) ) )
    } ) ),
    null
    );
FieldDeclarationSyntax bField = SF.FieldDeclaration( 
    SF.VariableDeclaration( 
        SF.ParseTypeName( "OtherClass" ), 
        SF.SeparatedList(new [] {
            SF.VariableDeclarator(
                SF.Identifier( "_b" ),
                null,
                SF.EqualsValueClause( initializationExpression ) 
            )
        } )
    ) )
    .AddModifiers( SF.Token( SyntaxKind.PrivateKeyword ) )
    .AddModifiers( SF.Token( SyntaxKind.ReadOnlyKeyword ) )
    ;
@class = @class.AddMembers( bField );

Generating the code:

private readonly OtherClass _b = new OtherClass(1, "abc");

Field-backed Properties

The following code generates a getter and setter backed by a field:

PropertyDeclarationSyntax property =
    SF.PropertyDeclaration( SF.ParseTypeName( "String" ), SF.Identifier( "A" ) )
        .AddModifiers( SF.Token( SyntaxKind.PublicKeyword ) )
        .AddAccessorListAccessors( 
            SF.AccessorDeclaration(
                SyntaxKind.GetAccessorDeclaration,
                SF.Block(
                    SF.List( new [] {
                        SF.ReturnStatement( SF.IdentifierName( "_a" ) )
                    } )
                )
            ),
            SF.AccessorDeclaration(
                SyntaxKind.SetAccessorDeclaration,
                SF.Block( 
                    SF.List( new [] {
                        SF.ExpressionStatement( 
                            SF.BinaryExpression( 
                                SyntaxKind.SimpleAssignmentExpression,
                                SF.IdentifierName( "_a" ),
                                SF.IdentifierName( "value" )
                            )
                        )
                    } )
                )
            )
        )
    ;

Producing the following code:

public String A {
    get {
        return _a;
    }

    set {
        _a = value;
    }
}

The ratio of code to output shows that it would be a lot easier to generate code from scratch with TextWriter.

Formatting C# with Roslyn

2

There’s already been a change to Roslyn that made my last bit of code obsolete. CustomWorkspace no longer requires a string in the constructor and is just:

new CustomWorkspace()

I found out how to change the formatting on the output. We can do this:

CustomWorkspace cw = new CustomWorkspace();
OptionSet options = cw.GetOptions();
options = options.WithChangedOption( CSharpFormattingOptions.OpenBracesInNewLineForMethods, false );
options = options.WithChangedOption( CSharpFormattingOptions.OpenBracesInNewLineForTypes, false );
SyntaxNode formattedNode = Formatter.Format( cu, cw, options );

With this change, our output code is now:

using System;
using System.Generic;

namespace MyNamespace {
    private partial class MyClass {
    }
}

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]