I had a question the other day that I ended up taking directly to the Roslyn issues: How do I get a list of all of the types available to a compilation? Schabse Laks (@Schabse) and David Glick (@daveaglick) introduced me to a cool class I hadn’t encountered before: The SymbolVisitor
.
In previous posts we touched on the CSharpSyntaxWalker
and the CSharpSyntaxRewriter
. The SymbolVisitor
is the analogue of SyntaxVisitor, but applies at the symbol level. Unfortunately unlike the SyntaxWalker
and CSharpSyntaxRewriter
, when using the SymbolVisitor
we must construct the scaffolding code to visit all the nodes.
To simply list all the types available to a compilation we can use the following.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class NamedTypeVisitor : SymbolVisitor | |
{ | |
public override void VisitNamespace(INamespaceSymbol symbol) | |
{ | |
Console.WriteLine(symbol); | |
foreach(var childSymbol in symbol.GetMembers()) | |
{ | |
//We must implement the visitor pattern ourselves and | |
//accept the child symbols in order to visit their children | |
childSymbol.Accept(this); | |
} | |
} | |
public override void VisitNamedType(INamedTypeSymbol symbol) | |
{ | |
Console.WriteLine(symbol); | |
foreach (var childSymbol in symbol.GetTypeMembers()) | |
{ | |
//Once againt we must accept the children to visit | |
//all of their children | |
childSymbol.Accept(this); | |
} | |
} | |
} | |
//Now we need to use our visitor | |
var tree = CSharpSyntaxTree.ParseText(@" | |
class MyClass | |
{ | |
class Nested | |
{ | |
} | |
void M() | |
{ | |
} | |
}"); | |
var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); | |
var compilation = CSharpCompilation.Create("MyCompilation", | |
syntaxTrees: new[] { tree }, references: new[] { mscorlib }); | |
var visitor = new NamedTypeVisitor(); | |
visitor.Visit(compilation.GlobalNamespace); |
In order to visit all the methods available to a given compilation we can use the following:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class MethodSymbolVisitor : SymbolVisitor | |
{ | |
//NOTE: We have to visit the namespace's children even though | |
//we don't care about them. 😦 | |
public override void VisitNamespace(INamespaceSymbol symbol) | |
{ | |
foreach(var child in symbol.GetMembers()) | |
{ | |
child.Accept(this); | |
} | |
} | |
//NOTE: We have to visit the named type's children even though | |
//we don't care about them. 😦 | |
public override void VisitNamedType(INamedTypeSymbol symbol) | |
{ | |
foreach(var child in symbol.GetMembers()) | |
{ | |
child.Accept(this); | |
} | |
} | |
public override void VisitMethod(IMethodSymbol symbol) | |
{ | |
Console.WriteLine(symbol); | |
} | |
} |
It’s important to be aware of how you must structure your code in order to visit all the symbols you’re interested in. By now you may have noticed that using this API directly makes me a little sad. If I’m interested in visiting method symbols, I don’t want to have to write code that visits namespaces and types.
Hopefully at some point we’ll get a SymbolWalker
class that we can use to separate out our implemenation from the traversal code. I’ve opened an issue on Roslyn requesting this feature. (It seems like it’s going to be challenging to implement and would require working with both syntax and symbols).
Finding All Named Type Symbols
Finally, you might be wondering how I answered my original question: How do we get a list of all of the types available to a compilation? My implementation is below:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class CustomSymbolFinder | |
{ | |
public List<INamedTypeSymbol> GetAllSymbols(Compilation compilation) | |
{ | |
var visitor = new FindAllSymbolsVisitor(); | |
visitor.Visit(compilation.GlobalNamespace); | |
return visitor.AllTypeSymbols; | |
} | |
private class FindAllSymbolsVisitor : SymbolVisitor | |
{ | |
public List<INamedTypeSymbol> AllTypeSymbols { get; } = new List<INamedTypeSymbol>(); | |
public override void VisitNamespace(INamespaceSymbol symbol) | |
{ | |
Parallel.ForEach(symbol.GetMembers(), s => s.Accept(this)); | |
} | |
public override void VisitNamedType(INamedTypeSymbol symbol) | |
{ | |
AllTypeSymbols.Add(symbol); | |
foreach (var childSymbol in symbol.GetTypeMembers()) | |
{ | |
base.Visit(childSymbol); | |
} | |
} | |
} | |
} |
I should note that after implementing this solution, I came to the conclusion that it was too slow for our purposes. We got a major performance boost by only visiting symbols within namespaces defined within source, but it was still about an order of magnitude slower than the simply searching for types via the SymbolFinder
class.
Still, the SymbolVisitor
class is probably appropriate for one-off uses during compilation or for visiting a subset of available symbols. At the very least, it’s worth being aware of.
I am regularly reading and enjoying your posts. However, using feedly, I have to go to your site every time, as feedly happens to never include the source code hosted with ❤ by GitHub. I first thought that this was feedly's fault. It isn't, as it is basing its rendering on the RSS feed which does not have the source code… Any idea why it is not showing up in the RSS feed (https://joshvarty.wordpress.com/category/uncategorized/feed/)? Is something wrong with the WordPress plug-in you are using? Is this by design?
It’s not by design, unfortunately it doesn’t seem like WordPress is embedding Gists in a nice way. 😦
I’ll try to figure out a workaround or maybe I’ll end up moving my code snippets to another service.
Thank you. Ideally, WordPress should be fixed, I guess 🙂
Are there any way to get the symbols list without a compilation? for example from the syntax tree.
Nope, as far as I know you can only get the semantic model from a compilation or from a Document. Then you can use the semantic model to get symbols from corresponding pieces of syntax.
Thanks, Josh. I appreciate the time you took to write these priceless posts. Since your series has turned to a Roslyn reference for many, I think you need to keep your content relevant and updated. For example, I think mentioning the discussion you had on Github around SymbolWalker could be enlightening about the graph structure of Symbols.
It seems to me that that you are multithreading (“Parallel.ForEach(symbol.GetMembers(), s => s.Accept(this));”) to update your list (“AllTypeSymbols.Add(symbol);”).
Please note that Listis not thread safe (https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1?view=netframework-4.7.2) and so you run the risk of a race condition causing a corrupted result in AllTypeSymbols ,
John posted
It’s been a while since your original post and I’m curious to know if you have found the speed is still an issue between symbol finder and the visitor pattern. In particular I’m looking to make graphs out of sections of the code that may span an entire solution.
It’s been a long time since I’ve used it so I can’t say for sure. I think it depends on how frequently you need to visit symbols in your compilation. If I remember correctly we were considering using it fairly frequently while a user was typing, which ended up being a problem.
If you’re hoping to look at all the symbols once (or less frequently than we were) the performance may not be an issue for you.