Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / All-Topics

Getting Started with Roslyn – Part 1

5.00/5 (2 votes)
12 Jan 2015CPOL11 min read 10.7K  
Getting Started with Roslyn – Part 1

I have not blogged for quite some time. Family, work, and life have all conspired to stop me finding the time to write here. I am now going to start writing again, tentatively, with a view to making it a more permanent endeavour. So, without further ado, I give you my first steps with Roslyn…

Much has already been written on Roslyn but I thought I would offer my own thoughts on getting started with this excellent tool being developed by Microsoft.

If you have yet to hear of the wonders of Roslyn, then go check it out (http://msdn.microsoft.com/en-us/vstudio/roslyn.aspx go on, I will wait). It is currently in CTP but is a workable ‘compiler as a service’ API – that is to say that it exposes the C# (and VB.NET if you are that way inclined) compiler as an API for developers to call and work with the syntax tree through.

Getting the CTP installed is relatively straight forward – simply download the setup file (https://www.microsoft.com/en-us/download/details.aspx?id=34685) and fire it up. You will need Visual Studio 2012 and the Visual Studio 2012 SDK installed (you will be prompted for the latter if it is not already installed).

Once this is all installed, you should have no problem starting Visual Studio and following along as we delve in and see just how exciting this tool really is.

We are going to build a simple console app which will run though some of the basics of using Roslyn and print out some interesting stuff to the screen. We will end by using it to change a simple item within a class (an XML comment) and save this out as a new class.

In part 2 of this overview, we will build a simple WPF app that will take a well-formed *.cs file and compile it on the fly to allow you to create instances of its types and call methods on them, but before that, we need to cover the basics.

Step 0 – Creating the Initial Console App and Sample Class File

When you start Visual Studio after installing Roslyn, you will not see any immediate difference but if you head up to File –> New –> Project, you will see a set of Roslyn templates.

Pro-Tip If you don’t see these, then you can start Visual studio with the following command line switches and try again:

“C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\devenv.exe” /rootsuffix roslyn

image

Select this template folder and create a new Console Application from within it. This will give you a new console app with the references that you need to get started with Roslyn.

Now that we have the app ready, we need to add a new class to the project. This class is going to be the one that we manipulate with Roslyn and as such, we don’t want it to compile with the rest of the project.

Add a new class, Person, and then right click on the file and choose properties, then set the Build Action to None and Copy to Output Directory to Copy Always. This is the class that we are going to be using.

C#
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace CSInRoslynPartOne
{
    public enum Sex
    {
        Male,
        Female
    }

    public class Person
    {
        public Person(string name, Sex sex, int age)
        {
            this.Name = name;
            this.Sex = sex;
            this.Age = age;
        }

        public Sex Sex { get; private set; }

        public string Name { get; private set; }

        public int Age { get; private set; }

        public bool IsAdult
        {
            get
            {
                return this.Age >= 18;
            }
        }

        /// <summary>
        /// Changes the persons name
        /// </summary>
        ///<param name="name">The name of the person</param>
        /// <returns>A new person representing the changed name</returns>
        public Person AlterName(string name)
        {
            return new Person(name, this.Sex, this.Age);
        }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(this.Name);
            sb.Append(" is a ");
            sb.Append(this.Age);
            sb.Append(" year old ");
            sb.Append(this.IsAdult ? "Adult " : "Child ");
            sb.Append(this.Sex);
            return sb.ToString();
        }
    }
}

Step 1 – Parsing the File

Now we have the basics in place. We need to start using the Roslyn API to read it. The main representation of a source file when it comes to using Roslyn is the SyntaxTree. It is the SyntaxTree that is the parsed display of C# code.

If the SyntaxTree represents the whole tree, then the CompilationUnitSyntax represents the bits we can look at in the file. In the case of our file, there is only a single compilation unit which represents the entire file contents.

You create a SyntaxTree by using either a string or a file on disk, we are going to be using the Person.cs file that we created in the previous step. From this SyntaxTree, we will create a CompilationUnitSyntax from the root node in order to move on with our overview of the kind of things you can do with Roslyn.

C#
SyntaxTree tree = SyntaxTree.ParseFile("Person.cs");
CompilationUnitSyntax root = tree.GetRoot();

Step 2 – So, What Can I Look At First

The CompilationUnitSyntax provides a large number of methods and properties that we can use to delve into the code, but we are going to start simple. The CompilationUnitSyntax has a Language property and this will tell us what language the file we have parsed is written in. We will print this out in our console app and wrap it up nicely in a line that looks a little more English than simply printing out the Language on its own.

C#
Console.WriteLine("the file is written in {0}", root.Language);

The CompilationUnitSyntax contains a collection property to see the descendants of the unit which we will get to in the next step but before we move on from looking at the members on this top level object, we will look at the using directives for the code file. We do this by looking at the Usings collection property – this is a SyntaxList<T> where T is of type UsingDirectiveSyntax, which has details of each using directive. From here, you can see the alias of the using if any have been set up and execute a number of methods to edit, view and remove the directive. We are simply going to print the name of the using directives to the screen.

C#
Console.WriteLine("The file's usings are:");
foreach (UsingDirectiveSyntax u in root.Usings)
{
    Console.WriteLine(u.Name);
}

Step 3 – Let's Look At Some File Parts

Seeing the language and the using directives in the file might look nice, but we need to start working with the different types of syntax members within the file, that are exposed by the Roslyn API, in order to find out any useful information about our code.

The CompilationUnitSyntax, along with other syntax types, exposes a Members property which allows us to see the contents of this syntax. Our sample class has a single member at this level, the namespace, which in turn has two members of its own, namely the enumeration and the class.

Let's print out this information along with the type and name of the members.

The Members property is another SyntaxList<T> but T is now of type MemberDeclarationSyntax. SyntaxList<T> implements IEnumerable so we can use Linq to get the member that we require. As we know that the root node is a namespace, we can explicitly declare that we want to find a NamespaceDeclarationSyntax, if we didn’t know what type of member we wanted then we could have simply used the MemberDecalrationSyntax until we have found more information about the syntax node of interest.

C#
NamespaceDeclarationSyntax ns = root.Members.Single
(m => m is NamespaceDeclarationSyntax) as NamespaceDeclarationSyntax;
Console.WriteLine("the root namespace is {0} 
and contains the following types:", ns.Name);

Once we have the namespace, we can loop through its Members collection and list them out. Again, as we know the types of members we want we can use BaseTypeDeclartionSyntax to display the name and kind of the member as both ClassDeclarationSyntax (via TypeDeclarationSyntax, along with StructDeclarationSyntax and InterfaceDeclarationSyntax) and EnumDeclarationSyntax ultimately derive from this class.

C#
foreach (BaseTypeDeclarationSyntax member in 
ns.Members.Cast<BaseTypeDeclarationSyntax>())
{
    Console.WriteLine("{0}, which is of 
    type '{1}'", member.Identifier, member.Kind);
}

The BaseTypeDeclarationSyntax exposes the Identifier and Kind (which is inherited from SyntaxNode) of the syntax object which we can use to display the details of this member to the screen.

Step 4 – Dive into Enumerations

Now that we know the different types in the root node, we can start to look at them directly. We know that we have an enumeration and a class so let's start with the enumeration. As we have already seen, it is defined by the EnumDeclarationSyntax type which, along with the other syntax types we have seen, exposes its members through a property called Members. Iterating through the collection allows us to access the values of the enumeration which are exposed as EnumMemberDeclarationSyntax objects, which, again, inherits from SyntaxNode and so exposes the Identifier property which we can use to list the names of the enumeration values.

C#
EnumDeclarationSyntax sexEnum = ns.Members.Single
(m =>; m is EnumDeclarationSyntax) as EnumDeclarationSyntax;
Console.WriteLine("The enum member has {0} 
elements and they are:", sexEnum.Members.Count());
foreach (EnumMemberDeclarationSyntax syntax in sexEnum.Members
{
    Console.WriteLine(syntax.Identifier);
}

As we can see, there is a pattern forming in the way that we access the different parts of the file. If we want to access a member as a certain type, then we must cast to that type before we use it.

Step 5 – Looking at Methods

The last member in our file is a class which is exposed by the ClassDeclarationSyntax type. We access this in the same way as the other types through the Members property. The class, in turn, has its own members collection and we can use this to access the methods, properties, and other parts of the class. The method has a property called ParameterList which exposes its own Parameters property of type SeparatedSyntaxList<T> where T is of type ParameterSyntax, It is this collection of ParameterSyntax that exposes the individual parameters of the method and, as with other syntax lists, it can be iterated through and the Identifier can be obtained. It also exposes the Type of the parameter.

C#
ClassDeclarationSyntax personClass = ns.Members.Single
(m => m is ClassDeclarationSyntax) as ClassDeclarationSyntax;
MethodDeclarationSyntax method = personClass.Members.OfType
<MethodDeclarationSyntax>().First(mds => mds.Identifier.ValueText == "AlterName");
SeparatedSyntaxList<ParameterSyntax> paramList = method.ParameterList.Parameters;
Console.WriteLine("The AlterName method has {0} parameters, 
and they are:", paramList.Count());

foreach (ParameterSyntax arg in paramList)
{
    Console.WriteLine("{0}, which is a {1} parameter", arg.Identifier, arg.Type);
}

That covers the basic outline of a file but this just touches on what we can do with Roslyn. The next couple of steps will cover looking at the non-code portions of a method, replacing a method’s XML header and then saving the changes back to a new file.

Step 6 – Investigating Trivia

Trivia to Roslyn is the whitespace and non-code elements of the file. We are going to use the trivia types to look at the whitespace and comment at the head of the AlterName method that we looked at in the previous step. There are several different methods for fetching trivia including leading, trailing, and descendant members trivia but we are going to look at the leading trivia as that is where the comment we are interested in is located.

When you look at the method, it appears that there is only one set of leading trivia for this method and that is the comment but there are actually four:

  • The end of the line for the previous method
  • The empty line between the previous method and the comment
  • The comment itself
  • The leading whitespace on the line with the method signature

To access this trivia, we call the GetLeadingTrivia method on the MethodDeclarationSyntax type which return a SyntaxTriviaList. This is not the same generic type that the members property returns but it implements IEnumerable so, again, we can iterate through the list to look at the different trivia elements at the start of the method. I have picked out the whitespace and newline elements so they display something on the screen rather than an empty space but other than that, the pattern is, again, much the same as before.

C#
SyntaxTriviaList trivia = method.GetLeadingTrivia();
Console.WriteLine("The AlterName method has the following trivia:");
foreach (SyntaxTrivia t in trivia)
{
    if (t.Kind == SyntaxKind.WhitespaceTrivia)
    {
        Console.WriteLine("''WHITESPACE''");
    }
    else if (t.Kind == SyntaxKind.EndOfLineTrivia)
    {
        Console.WriteLine("''END_OF_LINE''");
    }
    else
    {
        Console.WriteLine(t);
    }
}

Note that in the else block, we simply write the SyntaxTrivia to the console, this is because SyntaxTrivia provides a handy override of ToString() which writes out the text when the element is a comment.

Step 7 – Removing a Comment

Now that we have found our comment, we can edit it. Roslyn allows you to edit, remove and replace any element in the SyntaxTree but we are using a comment as it is the most straight-forward thing to remove.

We know that the trivia list only contains one comment so we can use this knowledge to get a reference to it. Once we have this SyntaxTrivia item, we can call the ReplaceTrivia method on the MethodDecalrationSyntax passing in the trivia we want to replace (the comment) and a list of trivia to replace it with. In our case, we are replacing it with an empty list (SyntaxTriviaList.Empty) as we want to remove the comment altogether.

C#
SyntaxTrivia comment = trivia.First(t => t.Kind == SyntaxKind.DocumentationCommentTrivia);
MethodDeclarationSyntax newAlterMethod = method.ReplaceTrivia(comment, SyntaxTriviaList.Empty);

As we can see, the ReplaceTrivia method actually returns a new MethodDecalrationSyntax rather than changing the current one, this is important, as we will see next.

Step 8 – Saving Out

The final stop on our whistle stop tour is saving the change we have made.

We first need to create a new SyntaxTree that incorporates the changed method. The SyntaxTree.Create() static method creates a SyntaxTree from a CompilationUnitSyntax.

The ReplaceNode method on the CompilationUnitSyntaxType (our original root object from above) operates in the same way as the ReplaceTrivia method in the previous step and takes the original and replacement elements and returns a new CompilationUnitSyntax.

Once we have the new tree, we can use standard .NET file handling methods to write the string of the CompilationUnitSyntax out to a new file which, in our case, is Person2.cs.

C#
SyntaxTree tree2 = SyntaxTree.Create(root.ReplaceNode(method, newAlterMethod));
using (StreamWriter file = File.CreateText("Person2.cs"))
{
    file.Write(tree2.ToString());
    file.Flush();
}

The code used in this small console app is available herenote that you will need to install the Roslyn bits as they are currently non-distributable components.

This has been a whirlwind tour of the top level features of the Roslyn API. I may have meandered through them but it is such an exciting topic that once I started to put together the sample app, I couldn’t stop putting more little bits in to show the power of this tool. As I have already mentioned, my next post will be an app to read in C# code and execute against the types and methods within. It won't be quite as long a wait as it has been for this post but the family, work and life are still there and the sample for it is only half complete :).

Thanks for reading, I hope you enjoyed it as much as I did!

Image 2 Image 3

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)