For ages I’ve been using T4 templates as main tool for code generation and scaffolding, but now that I’m an absolute fan of Visual Studio Code and .Net Core I need to explore other options such as Yeoman, Scripty and Roslyn. This post is just the result of my first and simplest experiment with .Net Core, Roslyn and Code Generation.

In the following sample we will take a class and replace the namespace with another. My intention is just to show you the tip of the iceberg of what you can accomplish with these tools!

1. Create the application#


Open a command prompt and run

1    md roslyn.codegeneration
2    cd roslyn.codegeneration
3    dotnet new
4    dotnet restore
5    code .

2. Replace the contents of project.json#


Replace the contents on project.json file in order to include the references to both: Microsoft.CodeAnalysis.CSharp and Microsoft.CodeAnalysis.Compilers.

 1{
 2  "version": "1.0.0-*",
 3  "buildOptions": {
 4    "debugType": "portable",
 5    "emitEntryPoint": true
 6  },
 7  "dependencies": {
 8    "Microsoft.CodeAnalysis.CSharp": "2.0.0-rc2",
 9    "Microsoft.CodeAnalysis.Compilers": "2.0.0-rc2"
10  },
11  "frameworks": {
12    "netcoreapp1.1": {
13      "dependencies": {
14        "Microsoft.NETCore.App": {
15          "type": "platform",
16          "version": "1.1.0"
17        }
18      },
19      "imports": "dnxcore50"
20    }
21  }
22}

3. Replace the contents of Program.cs#


The ChangeNamespaceAsync method is where the magic occurs. It takes the code in a string, parses it, replaces the namespace and finally writes the class to the console and a file.

 1using System;
 2using System.IO;
 3using System.Linq;
 4using System.Threading.Tasks;
 5using Microsoft.CodeAnalysis;
 6using Microsoft.CodeAnalysis.CSharp;
 7using Microsoft.CodeAnalysis.CSharp.Syntax;
 8
 9namespace Roslyn.CodeGeneration
10{
11    public class Program
12    {
13        public static void Main(string[] args)
14        {
15            // We will change the namespace of this sample code.
16            var code =
17            @"  using System; 
18
19                namespace OldNamespace 
20                { 
21                    public class Person
22                    {
23                        public string Name { get; set; }
24                        public int Age {get; set; }
25                    }
26                }";
27
28            // Use Task to call ChangeNamespaceAsync
29            Task.Run(async () =>
30            {
31                await ChangeNamespaceAsync(code, "NamespaceChangedUsingRoslyn");
32            })
33            .GetAwaiter()
34            .GetResult();
35
36            // Wait to exit.
37            Console.Read();
38        }
39
40        /// <summary>
41        /// Changes the namespace for the given code.
42        /// </summary>
43        static async Task ChangeNamespaceAsync(string code, string @namespace)
44        {
45            // Parse the code into a SyntaxTree.
46            var tree = CSharpSyntaxTree.ParseText(code);
47
48            // Get the root CompilationUnitSyntax.
49            var root = await tree.GetRootAsync().ConfigureAwait(false) as CompilationUnitSyntax;
50
51            // Get the namespace declaration.
52            var oldNamespace = root.Members.Single(m => m is NamespaceDeclarationSyntax) as NamespaceDeclarationSyntax;
53
54            // Get all class declarations inside the namespace.
55            var classDeclarations = oldNamespace.Members.Where(m => m is ClassDeclarationSyntax);
56
57            // Create a new namespace declaration.
58            var newNamespace = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName(@namespace)).NormalizeWhitespace();
59
60            // Add the class declarations to the new namespace.
61            newNamespace = newNamespace.AddMembers(classDeclarations.Cast<MemberDeclarationSyntax>().ToArray());
62
63            // Replace the oldNamespace with the newNamespace and normailize.
64            root = root.ReplaceNode(oldNamespace, newNamespace).NormalizeWhitespace();
65
66            string newCode = root.ToFullString();
67
68            // Write the new file.
69            File.WriteAllText("Person.cs", root.ToFullString());
70
71            // Output new code to the console.
72            Console.WriteLine(newCode);
73            Console.WriteLine("Namespace replaced...");
74        }
75    }
76}

4. Run the application#


Open a command prompt and run

1    dotnet run

The output should read:

 1using System;
 2
 3namespace NamespaceChangedUsingRoslyn
 4{
 5    public class Person
 6    {
 7        public string Name
 8        {
 9            get;
10            set;
11        }
12
13        public int Age
14        {
15            get;
16            set;
17        }
18    }
19}
20Namespace replaced...

Get a copy of the code here: https://github.com/cmendible/dotnetcore.samples/tree/main/roslyn.codegeneration

Hope it helps!