Access your C# Projects Through Code With Roslyn
Roslyn allows us to access our code through code. That permits us to analyse and work with code in a way that was impossible before. In this post we look at the basic parts of working with a Visual Studio solution to figure out what parts make up our application.
The syntax of Roslyn has not changed much in the 5 years since my first experiments. However, the same is not true for the dependencies and the things that work behind the API. Here we need a new combination of Roslyn and its dependencies to explore our solutions through code.
Preparations for .Net 7
Make sure that your Roslyn Project is on .Net 7 and add these 3 packages using the Package Manager console:
Install-Package Microsoft.Build.Locator -Version 1.5.5
Install-Package Microsoft.CodeAnalysis.CSharp.Workspaces -Version 4.4.0
Install-Package Microsoft.CodeAnalysis.Workspaces.MSBuild -Version 4.4.0
Those versions work together, but they will not work in .Net 6 or any other older version.
Read your solution file
The simplest starting point is to load the solution into a workspace. That allows us to iterate through our projects and list them on the console:
For my NUnit solution the code will produce this output:
Project MyBizLogic [0967cfce-a386-41ac-965f-aefc512ecf37]
Project TestsForNUnit2 [e1a15f36-4f86-44ac-910e-705ef47bb1bd]
Project TestsForNUnit3 [37fec393-c860-4c20-9aa8-f9490aaff268]
Find the project dependencies
We can iterate over our projects in the solution and explore the projects it depends on through the property AllProjectReferences:
That gives us this output:
Project ‘Microsoft.CodeAnalysis.Project’ depends on:
Project ‘Microsoft.CodeAnalysis.Project’ depends on:
– 0967cfce-a386-41ac-965f-aefc512ecf37
Project ‘Microsoft.CodeAnalysis.Project’ depends on:
– 0967cfce-a386-41ac-965f-aefc512ecf37
The GUID to reference the projects is great to compile code, but not so much to understand what is going on. A simple fix for a human-readable name is to iterate twice through your project list, the first time to create a dictionary that maps the GUID to the project name, and then we iterate through the projects to show their dependencies:
The output shows us that both our test projects reference the MyBizLogic project – this time in an understandable manner:
Project ‘Microsoft.CodeAnalysis.Project’ depends on:
Project ‘Microsoft.CodeAnalysis.Project’ depends on:
– MyBizLogic
Project ‘Microsoft.CodeAnalysis.Project’ depends on:
– MyBizLogic
Unfortunately, there is no way to access the referenced packages from Roslyn.
Helper methods for classes and interfaces
We can access interfaces and classes through the same methods. Therefore, I created these 3 helper methods to write the code only once:
List the interfaces
With the helper methods in place, we can filter the nodes for the type InterfaceDeclarationSyntax and iterate through the results:
When we run the code, we get the interface and its methods:
List the classes
To get the classes in a project, we filter for ClassDeclarationSyntax and iterate through the result:
This shows us all classes and their methods in our solution:
Classes in MyBizLogic:
– [DebtCalculator – class]
* ByMethod()
* BatchProcessing()
– [MyException – class]
Classes in TestsForNUnit2:
– [DebtCalculatorTestOld – class]
* SetUp()
* Calculation_can_only_be_called_with_valid_CalculatorMethods()
* Extended_calculation_isnt_allowed()
* Data_can_be_loaded_from_file_system()
* Something()
Classes in TestsForNUnit3:
– [DebtCalculatorTestNew – class]
* SetUp()
* Calculation_can_only_be_called_with_valid_CalculatorMethods()
* Extended_calculation_isnt_allowed()
* Extended_calculation_isnt_allowed_CheckInstanceOf()
* Data_can_be_loaded_from_file_system()
* Something()
List the enumerations
To get the definitions for enumerations in a project, we need to filter for EnumDeclarationSyntax and use the Members property to get the named constants:
This lists us the enumerations, its defined constants and their corresponding int value:
Next
Roslyn allows us to work with our code base through code. We can access classes, interfaces and projects without writing much code. From the basic examples in this post, you can build on and dive a lot deeper into the other packages that come with Roslyn.
Next week we use Roslyn to create a dependency graph of our projects and write a bit of Python code to analyse it.