.NET Core, Roslyn and Code Generation
Categories:
3 minute read
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!
Create the application
Open a command prompt and run
md roslyn.codegeneration
cd roslyn.codegeneration
dotnet new
dotnet restore
code .
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.
{
"version": "1.0.0-*",
"buildOptions": {
"debugType": "portable",
"emitEntryPoint": true
},
"dependencies": {
"Microsoft.CodeAnalysis.CSharp": "2.0.0-rc2",
"Microsoft.CodeAnalysis.Compilers": "2.0.0-rc2"
},
"frameworks": {
"netcoreapp1.1": {
"dependencies": {
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.1.0"
}
},
"imports": "dnxcore50"
}
}
}
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.
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Roslyn.CodeGeneration
{
public class Program
{
public static void Main(string[] args)
{
// We will change the namespace of this sample code.
var code =
@" using System;
namespace OldNamespace
{
public class Person
{
public string Name { get; set; }
public int Age {get; set; }
}
}";
// Use Task to call ChangeNamespaceAsync
Task.Run(async () =>
{
await ChangeNamespaceAsync(code, "NamespaceChangedUsingRoslyn");
})
.GetAwaiter()
.GetResult();
// Wait to exit.
Console.Read();
}
/// <summary>
/// Changes the namespace for the given code.
/// </summary>
static async Task ChangeNamespaceAsync(string code, string @namespace)
{
// Parse the code into a SyntaxTree.
var tree = CSharpSyntaxTree.ParseText(code);
// Get the root CompilationUnitSyntax.
var root = await tree.GetRootAsync().ConfigureAwait(false) as CompilationUnitSyntax;
// Get the namespace declaration.
var oldNamespace = root.Members.Single(m => m is NamespaceDeclarationSyntax) as NamespaceDeclarationSyntax;
// Get all class declarations inside the namespace.
var classDeclarations = oldNamespace.Members.Where(m => m is ClassDeclarationSyntax);
// Create a new namespace declaration.
var newNamespace = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName(@namespace)).NormalizeWhitespace();
// Add the class declarations to the new namespace.
newNamespace = newNamespace.AddMembers(classDeclarations.Cast<MemberDeclarationSyntax>().ToArray());
// Replace the oldNamespace with the newNamespace and normailize.
root = root.ReplaceNode(oldNamespace, newNamespace).NormalizeWhitespace();
string newCode = root.ToFullString();
// Write the new file.
File.WriteAllText("Person.cs", root.ToFullString());
// Output new code to the console.
Console.WriteLine(newCode);
Console.WriteLine("Namespace replaced...");
}
}
}
Run the application
Open a command prompt and run
dotnet run
The output should read:
using System;
namespace NamespaceChangedUsingRoslyn
{
public class Person
{
public string Name
{
get;
set;
}
public int Age
{
get;
set;
}
}
}
Namespace replaced...
Get a copy of the code here: https://github.com/cmendible/dotnetcore.samples/tree/main/roslyn.codegeneration
Hope it helps!