Static code analyzers are tools used to analyze software code without executing it. They can examine the code to find code smells, vulnerabilities, potential errors, and code out of a defined standard, for example. They work by parsing the source code and evaluating its syntax (structure of the code) and semantic (meaning of the code).
Roslyn, the C# compiler, provides tools for developing Roslyn Analyzers (Static code analyzers for Roslyn), giving access to the syntax and semantic of the code, that can be executed at development and build time, providing feedback in near real-time to the developers.
In this post, I’ll show a Roslyn Analyzer I built for using Discriminated Unions in C#, requiring to check the union type before access, and talk about some lessons learned while developing it.
The package allows the use of Discriminated Unions by enforcing two rules:
- The type of a Union must be checked before access;
- All types of a Union must be checked (or have an
To use the package, install it in the project:
<PackageReference Include="DiscriminatedUnions.Net" Version="126.96.36.199" />
UnionValue when declaring the types that will be used in the Discriminated Union:
Union member passing the possible value types:
Union<Dog, Bird> animal = new Dog();
And access it checking for the type with
Accessing the object without checking will yield an error
DUN002 - 'animal.Value' not checked before access:
Not checking for all possible types will yield an error
DUN001 - 'animal' not being evaluated for all possible types:
⚠️ This package was made for study purposes only. Feel free to test it, but won’t use it for production code as it won’t be maintained and may not cover all edge cases.
Roslyn analyzers can be distributed as nuget packages, but it is important to generate the package as a Development Dependency only, setting the property
true on the
This will generate two tags in the nuget package properties of the consumer projects:
PrivateAssetswith the value
all, indicating that this package won’t flow to projects depending on the consumer project. For example, in a scenario where
Project1depends on the
Project1, the analyzer won’t be available for
compilevalue in its value, indicating the consumer of the package won’t have access to its compiled assemblies.
A great use case for Roslyn Analyzers is to enforce rules on nuget packages.
The DistributedUnion.Net package is one example. It has the
UnionValue types and analyzers to enforce the rules on its use.
Because classes inside Analyzers packages are not accessible by consumers (and shouldn’t be), the most correct way, in my opinion, is to have the public classes in one package and this package will consume another package with the analyzers, but removing the
PrivateAssets configuration. This way, the analyzers will be available for consumers of the parent package:
Because analyzers use the code syntax, as new ways of doing something are added to the language, analyzers have to be updated to treat them also. For example:
- An analyzer checking for conditions before pattern matching was release, would need to be updated to consider this new syntax;
- An analyzer using types for its rules, would need to be updated to consider nullable reference types in its logic.
The Roslyn Analyzers project template comes with a VSIX project that can be run to test the analyzers, but I couldn’t get them to work and decided it wasn’t worth the time.
Turns out the best and easiest way to run and experiment with the analyzer is by running automated tests. Create the test, run the test and change the analyzer until the test passes.
Example of an automated test that expects the error code
DUN001 at the location
0 with the argument
The location and argument are marked with the
ℹ️ The project template comes with some basic tests as examples that are easy to change.
The analyzers methods and properties define general interface types that need to be cast to the specific type before accessing its properties.
This is an excerpt from the DiscriminatedUnions.Net package:
The easiest way to understand the syntax tree is by using the Visual Studio’s Syntax Visualizer (Installation instructions).
Just click anywhere in the code and it will show the syntax tree up to that point:
Another tip is to type
IOperation, or any base interface to see all the specific interfaces in the intellisense:
Because Roslyn Analyzers have very specific use cases, there is not a lot of materials and documentation online. Here are some links that helped me learn (specially Josh Varty’s and Meziantou’s Blog):