The more I work with Roslyn the more I appreciate the possibilities it gives and the more I hate it. And I hate it for the same thing as many other projects I worked with in the past. What is it? Well, I like when a system
fails fast,
fails loudly and
fails in the clear way. Unfortunately, Roslyn can do something completely different what sometimes makes working with it the pain in ass. I'll give you some examples.
Issue 1 - Problem with "empty" projects
Here is the code that shows how I usually process documents/files for a given project. It's pretty easy.
var workspace = MSBuildWorkspace.Create();
var sln = await workspace.OpenSolutionAsync(path);
foreach (var projectId in sln.ProjectIds)
{
var project = sln.GetProject(projectId);
foreach (var documentId in project.DocumentIds)
{
// Process a document
}
}
It works quite well but only on my machine :) On 2 other machines I'm observing problems. In general I have an example solution with 2 test projects. One is WPF application and the another is WebAPI.
The problem is that on some machines I can only read and analyze WPF application. If I try to do exactly the same thing with WebAPI application, then the project loaded by Roslyn is empty i.e. contains no documents (
DocumentIds property is empty)! I've already tried to load this project in a different way but without success.
To be honest currently I'm stuck and I have no idea what is wrong here. Any suggestions?
Issue 2 - the semantic analysis does not work
With Roslyn we can perfrom the syntax analysis and the semantic analysis of the code. The syntax analysis, with a syntaxt tree, allows you to only see a structure of a program. The semantic analysis is more powerfull and allows you to understand more. For example, having a code like that:
SomeClass x;
With the semantic analysis you can check that
SomeClass is defined within
SomeNamespace and has X members (methods, properties). For example, here we have a code showing how to use the semantic analysis to check what interfaces are implemented by a given class at any level of the inheritance.
var compilation = await project.GetCompilationAsync();
foreach (var documentId in project.DocumentIds)
{
var document = project.GetDocument(documentId);
// Get a syntax tree
var tree = await document.GetSyntaxTreeAsync();
// Get a root of the syntax tree
var root = await tree.GetRootAsync();
// Find a node of the syntaxt tree for a first class in a file/document
var classNode= root.DescendantNodes().OfType<ClassDeclarationSyntax>().FirstOrDefault();
if(classNode== null) continue;
// Get a semantic model for the syntax tree
var semanticModel = compilation.GetSemanticModel(tree);
// Use the semantic model to get symbol info for the found class node
var symbol = semanticModel.GetDeclaredSymbol(classNode);
// Check what inerfaces are implemnted by the class at any level
foreach(var @interface in symbol.AllInterfaces)
{
// ...
}
}
If you run this code as it is, it again will not throw any exceptions. However, you'll noticed that any found class doesn't implement any interface according to Roslyn. Where is the problem this time?
It's quite obvious if you know that. To perform the semantic analysis Roslyn needs to analyse assemblies used by the project. However, it's not enough to compile the project. You have to explicitly register all required assemblies. I do it in the easy way. I simply register all assemblies found in the output folder.
var compilation = await project.GetCompilationAsync();
// Let's register mscorlib
compilation = compilation.AddReferences(MetadataReference.CreateFromFile(typeof(object).Assembly.Location));
if (Directory.Exists("PATH TO OUTPUT DIRECTORY"))
{
var files = Directory.GetFiles(directory, "*.dll").ToList(); // You can also look for *.exe files
foreach (var f in files)
compilation = compilation.AddReferences(MetadataReference.CreateFromFile(f));
}
And again if the semantic analysis can not be performed without that why no exception is thrown?
Issue 3 - Problem with reading projects/solutions
This one I've already described in more details in the
post about Roslyn and unit tests. The problem was that:
- MSBuildWorkspace.OpenSolutionAsync method was returning an empty solution if a particular assembly was missing (not fast, not loud)
- MSBuildWorkspace.OpenProjectAsync method was returning the error The language 'C#' is not supported (not in the clear way).
These issues were caused by a missing assembly i.e.
Microsoft.CodeAnalysis.CSharp.Workspaces.dll. However, wouldn't it be easier to just throw an exception saying that it is missing. Or at least saying that it was not possible to find assembly responsible for reading C# projects and solutions.
Remember failing fast, loudly and in the clear way does not cost much but can save a lot of time.
*The picture at the beginning of the post comes from own resources and shows cliffs near Cabo da Roca - the westernmost extent of mainland Portugal.