Introduction
In the previous post we have learned how to create a syntax tree, however, sometimes, we want to change the code, and it could be done through changing the syntax tree; since this is a bidirectional relationship. There are two ways to achieve this goal, one is using methods directly available at the instances of types provided by Roslyn, another approach is to use SyntaxRewriter
in conjunction with them!
Syntax Tree
Let's consider we have the following code, and we want to change it in a way that if the return type of a method is void
it should be changed to a Task
, and if it is any other type, it should not be touched!
public class Sample
{
public void Method(){}
internal void Method2(){}
private int Method3(){ return 0;}
public class Nested
{
public void Method(){}
public int Method2(){ return 0;}
}
}
Using SharpLab the syntax tree should be something similar to the following image; what we are interested in the syntax tree is the marked nodes, MethodDeclaration
, which their representative type in the code is MethodDeclarationSyntax
, they have a sub-node as ReturnTypeSyntax
.

the node selected in yellow will be replaced by a new one that is returning a task!

Replacement Methods
To achieve that, first we need to get the root node, CompilationUnit
, and then select every child node that is of type MethodDeclarationSyntax
, since that is the target node in our scenario; The firs argument of the DescendantNodes
method, indicates whether it should descend into the children of the selected node or not! The following code, selects all the MethodDeclaration
nodes, even those in the Nested
class.
var syntaxTree = CSharpSyntaxTree.ParseText(code);
// syntaxTree = SyntaxFactory.ParseSyntaxTree(code);
var root = syntaxTree.GetRoot();
var methods = root
.DescendantNodes(_ => true)
.OfType<MethodDeclarationSyntax>();
Now that we have all the tree nodes that we want to replace with a new one, we could use the method ReplaceNodes
on the root instance, keep in mind, that this is not an in-place replacement as everything in Roslyn is immutable; that means not only we should use the replace method variations, but also every time we are doing so, we should assign the result to a new variable.
var newRoot = root
.ReplaceNodes(methods, (original, _) =>
{
// Not shown for the berevity of the code, check further below in the post
});
var newSyntaxTree = syntaxTree.WithRootAndOptions(newRoot, syntaxTree.Options);
Console.WriteLine(newSyntaxTree);
The ReplaceNodes
method accepts an IEnumerable<SyntaxNode>
and a Func
that should return a SyntaxNode
replacing the original one! In our scenario, if the original
node does not have a return type of void
, we will return it without changing anything, if it does, we will change it to Task
, the following is the body of the lambda function passed as the second argument to the ReplaceNodes
method, in the above-mentioned code:
var returnType = original.ReturnType;
if (returnType.GetFirstToken().Text != SyntaxFactory.Token(SyntaxKind.VoidKeyword).Text)
return original;
var identifierToken = SyntaxFactory.Identifier("Task")
.WithTriviaFrom(returnType.GetFirstToken());
var identifierNameSyntaxNode = SyntaxFactory.IdentifierName(identifierToken);
return original.WithReturnType(identifierNameSyntaxNode);
It is worth mentioning that, every syntax token might have some syntax trivia attached to it, as we know, the trivia does not contribute to the semantic of the code, but to the formatting and layout, if we define the identifierToken
in the code above, without attaching the trivia from the returnType
token, then there would be no space between Task
and the method names.
There are various methods available on instances of the objects in the tree that we could leverage to create new syntax nodes, tokens, or trivias, and form a new syntax tree representing completely difference source code. After writing all the above code, you could run the application and see the result, it should be similar to the following:

Syntax Rewriter
Now, lets take a look into another approach using visitor classes, by creating a syntax rewriter that inherits from CSharpSyntaxRewriter
. CSharpSyntaxRewriter
is a class that extends CSharpSyntaxVisitor<SyntaxNode>
and provides default implementations for visiting various C# syntax nodes. By this inheritance there are several VistiXYZ
methods to override, XYZ, could be any type we are interested in from the syntax tree, in case of our scenario and MethodDeclaration
, it will be VisitMethodDeclaration
, and the overridden method should return a SyntaxNode
.
Since the logic is the same as the lambda function passed to the ReplaceNodes
method, here is the SyntaxRewriter
class:
public sealed class MethodDeclarationSyntaxRewriter : CSharpSyntaxRewriter
{
public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax original)
{
var returnType = original.ReturnType;
if (returnType.GetFirstToken().Text != SyntaxFactory.Token(SyntaxKind.VoidKeyword).Text)
return original;
var identifierToken = SyntaxFactory.Identifier("Task")
.WithTriviaFrom(returnType.GetFirstToken());
var identifierNameSyntaxNode = SyntaxFactory.IdentifierName(identifierToken);
return original.WithReturnType(identifierNameSyntaxNode);
}
}
After defining the rewriter class, the usage is as straight forward as creating an instance of it and passing the root
object to its Visit
method; the result of this code should be identical with the one used in the Replacement Methods section.
var syntaxTree = CSharpSyntaxTree.ParseText(code);
// syntaxTree = SyntaxFactory.ParseSyntaxTree(code);
var root = syntaxTree.GetRoot();
var rewriter = new MethodDeclarationSyntaxRewriter();
var newRoot = rewriter.Visit(root);
var newSyntaxTree = syntaxTree.WithRootAndOptions(newRoot, syntaxTree.Options);
Console.WriteLine(newSyntaxTree);
By overriding the Visit*
methods and returning new nodes, you can alter the structure of the code while preserving unmodified parts, and remember to override selectively and only parts you need to analyze.
Conclusion
In scenarios where you need automated code transformations such as code fixes, refactorings, or advanced source generation, you could use either of the above-mentioned approaches, Replacement methods, or syntax rewriter. The latter is a bit more performant, as you could change the node while traversing the tree. however, always bear in mind, that in neither of these approaches, the original tree or nodes are not changed and a new one is created due to immutability nature of the Roslyn APIs. Also, using SyntaxTreeRewriter
aligns with Roslyn’s immutable design, making it easy to produce entirely new ASTs. Last but not least, always consider whether you should preserve existing trivia!
Thanks for reading so far, enjoy coding and Dametoon Garm [1] 😊