- •Table of Contents
- •About the Author
- •Acknowledgments
- •Introduction
- •Version Support
- •Supported Versions
- •A Unified Platform
- •Roadmap
- •Supported Operating Systems
- •Command Line Interface
- •Desktop Development
- •Blazor
- •MAUI
- •Wrapping Up
- •.NET 6 Architecture
- •Runtimes
- •CoreCLR
- •Mono
- •WinRT
- •Managed Execution Process
- •Desktop Packs
- •Wrapping Up
- •Dotnet New
- •Dotnet Restore
- •NuGet.config
- •Dotnet Build
- •Dotnet Publish
- •Dotnet Run
- •Dotnet Test
- •Using the CLI in GitHub Actions
- •Other Commands
- •Wrapping Up
- •WinAPI
- •WinForms
- •STAThread
- •WinForms Startup
- •DPI Mode
- •Responding to Scale Events
- •Visual Styles
- •Text Rendering
- •The Message Loop
- •The Form Designer
- •WPF Startup
- •XAML Layout
- •Visual Tree
- •Data Binding
- •Windows App SDK
- •Building a Windows App SDK application
- •Using Windows APIs with Windows App SDK
- •Packaging
- •Migrating to .NET 6
- •Upgrade Assistant
- •Wrapping Up
- •Blazor WebAssembly
- •Creating a Blazor Wasm Project
- •Blazor Progressive Web Apps
- •Exploring the Blazor Client Project
- •Blazor in .NET 6
- •Blazor Component System
- •Creating Blazor Pages
- •Running a Blazor App
- •Blazor Server
- •SignalR
- •Blazor Desktop
- •Wrapping Up
- •Project Structure
- •Exploring MAUI
- •The Cross-Platform World
- •Application Lifecycle
- •MVVM
- •MVVM Toolkit
- •Wrapping Up
- •Model-View-Controller
- •Routing
- •Views
- •Controllers
- •Controller-Based APIs
- •Minimal APIs
- •Wrapping Up
- •Web Apps
- •Creating an App Service
- •Static Web Apps
- •Web App for Containers
- •Docker
- •Azure Functions
- •Deploying Azure Functions
- •Wrapping Up
- •Record Types
- •Monolith Architecture
- •Microservices
- •Container Orchestration
- •Kubernetes
- •Docker Compose
- •Dapr
- •Installing Dapr
- •Dapr State Management
- •Wrapping Up
- •Roslyn
- •Compiler API
- •Diagnostic API
- •Scripting API
- •Workspace API
- •Syntax Tree
- •Roslyn SDK
- •Source Generators
- •Writing a Source Generator
- •Debugging Source Generators
- •Wrapping Up
- •Garbage Collector
- •The Heap
- •The Stack
- •Garbage Collection
- •A Look at the Threadpool
- •Async in .NET 6
- •Await/Async
- •Cancellations
- •WaitAsync
- •Conclusion
- •Index
Chapter 10 .NET Compiler Platform
•\ Syntax trees are thread-safe and immutable. A syntax tree is a state snapshot of the code. In-framework factory methods make sure that requested changes are pushed back to the code and a new syntax tree is generated based on the latest state of the source code. Syntax trees have a lot of optimalizations in place so that new instances of a tree can be generated very fast with little memory use.
Roslyn SDK
As mentioned before, Roslyn is extendable. To be able to develop your own Roslyn analyzers, you need to install the Roslyn SDK. The SDK is part of the Visual Studio installer; it’s available as an optional item as seen in Figure 10-4.
Figure 10-4. Installing the .NET Compiler Platform SDK
With the SDK comes the Syntax Visualizer. The Syntax Visualizer is an extra window in Visual Studio, under View Other Windows Syntax Visualizer, that lays out the syntax tree of the current open code file in Visual Studio. Its position synchronizes with your cursor in the source file. Figure 10-5 shows the visualizer docked to the side in Visual Studio 2022.
279
Chapter 10 .NET Compiler Platform
Figure 10-5. Syntax Visualizer
280
Chapter 10 .NET Compiler Platform
Figure 10-6. Roslyn project templates
The Syntax Visualizer is a great visual aid when working with the syntax tree. After installing the .NET Compiler Platform SDK, you will have access to new project templates in Visual Studio.
With these templates, you can build your own analyzers, code fixes, or code refactorings, both as a stand-alone console application and as a Visual Studio extension in the VSIX format. These templates are available for both C# and Visual Basic.
281
Chapter 10 .NET Compiler Platform
Creating anAnalyzer
Let’s create a stand-alone code analysis. Notice that the code analysis tools need to be written in .NET Framework; don’t worry about that; they do support analyzing .NET 6 code. The default template queries the available version of MSBuild on your system and lists them to select which version you want to analyze code against. Figure 10-7 shows the default output when we run the unchanged template; this list might be different for you depending on what is installed on your system.
Figure 10-7. Listing the available MSBuild instances
The logic of detecting MSBuild instances is abstracted away by the Roslyn SDK; all we need to do is call MSBuildLocator.QueryVisualStudioInstances().ToArray() to get a list of versions installed. Let’s empty the Main method and start implementing a code analyzer ourselves.
When analyzing code, we will need a SyntaxTree object. A SyntaxTree holds a parsed representation of a code document. In our example, we will inspect a piece of code and print the using statements in the console. Once we have our syntax tree
parsed, we can extract a CompilationUnitSyntax object. This object represents our code document, divided into members, using directives and attributes.
Listing 10-1 shows how to get the syntax tree and compilation unit from a piece of code.
282
Chapter 10 .NET Compiler Platform
Listing 10-1. Generating the syntax tree and compilation unit
static Task Main(string[] args)
{
const string code = @"using System; using System.Linq; Console. WriteLine(""Hello World"");";
SyntaxTree tree = CSharpSyntaxTree.ParseText(code); CompilationUnitSyntax root = tree.GetCompilationUnitRoot();
We are using a very simple code example to get the point across. We parse the code into a SyntaTree and extract the CompilationUnitRoot from there.
Next we will need a CSharpSyntaxWalker object. A syntax walker is an implementation of the Visitor design pattern. The Visitor pattern describes a way to decouple an object structure from an algorithm; more information on the pattern is found at https://en.wikipedia.org/wiki/Visitor_pattern.
The CSharpSyntaxWalker class is an abstract class so we will need to create our own class that inherits from CSharpSyntaxWalker. For this example, we add a class called UsingDirectivesWalker. Listing 10-2 shows the code for this class.
Listing 10-2. Custom using directive syntax walker
class UsingDirectivesWalker : CSharpSyntaxWalker
{
public override void VisitUsingDirective(UsingDirectiveSyntax node)
{
Console.WriteLine($"Found using {node.Name}.");
}
}
In this example, we are overriding the VisitUsingDirective method from the CSharpSyntaxWalker base class. The base class comes with many override methods that each visits a specific type of syntax nodes. The VisitUsingDirective method visits all using directives in our syntax tree. The complete list of methods that can be overwritten is found at https://docs.microsoft.com/en-us/dotnet/api/microsoft. codeanalysis.csharp.csharpsyntaxwalker.
For each using node we visit, we print its name. All there is left now is to use this custom syntax walker. Listing 10-3 shows the complete Main method.
283
Chapter 10 .NET Compiler Platform
Listing 10-3. Using the UsingDirectivesWalker
static Task Main(string[] args)
{
const string code = @"using System; using System.Linq; Console. WriteLine(""Hello World"");";
SyntaxTree tree = CSharpSyntaxTree.ParseText(code); CompilationUnitSyntax root = tree.GetCompilationUnitRoot();
var collector = new UsingDirectivesWalker(); collector.Visit(root);
Console.Read();
return Task.CompletedTask;
}
We instantiate our new syntax walker class and call its Visit method, passing in the
CompilationUnitSyntax. This triggers the methods in the CSharpSyntaxWalker base class, from which one is overwritten in our own syntax walker class. This results in the output visible in Figure 10-8.
Figure 10-8. Analyzer output
This has been a very simple example of how to extract a specific piece of code from a code snippet. This should help you get started with Roslyn. If you want to read more and dive deeper into Roslyn, the complete Roslyn documentation is a great resource: https://docs.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/.
284