10
\$\begingroup\$

The intent of the below code is to find all interfaces within a solution whose fully-qualified names match the given predicate. It seems to work, but as I am new to Roslyn I'm guessing there are things that could be improved. I'd appreciate any pointers.

public async Task<IImmutableList<INamedTypeSymbol>> GetMatchingInterfacesInSolution(string solutionPath, Func<string, bool> predicate)
{
    var workspace = MSBuildWorkspace.Create();
    var solution = await workspace.OpenSolutionAsync(solutionPath);
    var compilations = await Task.WhenAll(solution.Projects.Select(x => x.GetCompilationAsync()));

    return compilations
        .SelectMany(x => x.SyntaxTrees.Select(y => new { Compilation = x, SyntaxTree = y }))
        .Select(x => x.Compilation.GetSemanticModel(x.SyntaxTree))
        .SelectMany(
            x => x
                .SyntaxTree
                .GetRoot()
                .DescendantNodes()
                .OfType<InterfaceDeclarationSyntax>()
                .Select(y => x.GetDeclaredSymbol(y)))
        .Where(x => predicate(x.ToDisplayString()))
        .ToImmutableList();
}

Here's another approach I tried. I couldn't really see any benefit to doing it this way. It's more verbose and difficult to follow:

public static async Task<IImmutableList<INamedTypeSymbol>> GetMatchingInterfacesInSolution(string solutionPath, Func<string, bool> predicate)
{
    var workspace = MSBuildWorkspace.Create();
    var solution = await workspace.OpenSolutionAsync(solutionPath);
    var compilations = await Task.WhenAll(solution.Projects.Select(x => x.GetCompilationAsync()));

    return compilations
        .Select(x => x.Assembly.GlobalNamespace)
        .SelectMany(x => FindInterfacesRecursive(x, predicate))
        .ToImmutableList();
}

private static IEnumerable<INamedTypeSymbol> FindInterfacesRecursive(INamespaceSymbol @namespace, Func<string, bool> predicate)
{
    foreach (var member in @namespace.GetMembers())
    {
        var childNamespace = member as INamespaceSymbol;

        if (childNamespace != null)
        {
            foreach (var @interface in FindInterfacesRecursive(childNamespace, predicate))
            {
                yield return @interface;
            }

            continue;
        }

        var namedType = member as INamedTypeSymbol;

        if (namedType == null || namedType.TypeKind != TypeKind.Interface)
        {
            continue;
        }

        if (predicate == null || predicate(namedType.ToDisplayString()))
        {
            yield return namedType;
        }
    }
}
\$\endgroup\$
4
  • 1
    \$\begingroup\$ What version of Roslyn are you using? This doesn't compile for me under 1.0.0-rc1 unless I change the return type to Task<IImmutableList<ISymbol>>. \$\endgroup\$
    – mjolka
    Mar 25, 2015 at 2:46
  • \$\begingroup\$ @mjolka: I am using 1.0.0-rc1. Not sure why it's not compiling for you - this is my exact code and it compiles fine. I am compiling for FX4.5 - you? \$\endgroup\$ Mar 25, 2015 at 2:59
  • \$\begingroup\$ Very strange. Yes, targeting .NET 4.5 -- I've put the code up here. \$\endgroup\$
    – mjolka
    Mar 25, 2015 at 3:16
  • \$\begingroup\$ I haven't used this myself, but I have a feeling that that the SymbolFinder class, and especially the SymbolFinder.FindImplementationsAsync static method will be of some help to you. \$\endgroup\$
    – Phil Gref
    Apr 8, 2015 at 23:10

2 Answers 2

4
\$\begingroup\$
  • Name captured variables as you would name local variables.
  • First SelectMany and Select can be merged into a single SelectMany.

code:

public async Task<IImmutableList<ISymbol>> GetMatchingInterfacesInSolution(string solutionPath, Func<string, bool> predicate)
{
    var workspace = MSBuildWorkspace.Create();
    var solution = await workspace.OpenSolutionAsync(solutionPath);
    var compilations = await Task.WhenAll(solution.Projects.Select(x => x.GetCompilationAsync()));

    return compilations
        .SelectMany(compilation => compilation.SyntaxTrees.Select(syntaxTree => compilation.GetSemanticModel(syntaxTree)))
        .SelectMany(
            semanticModel => semanticModel
                .SyntaxTree
                .GetRoot()
                .DescendantNodes()
                .OfType<InterfaceDeclarationSyntax>()
                .Select(interfaceDeclarationSyntax => semanticModel.GetDeclaredSymbol(interfaceDeclarationSyntax)))
        .Where(symbol => predicate(symbol.ToDisplayString()))
        .ToImmutableList();
}
\$\endgroup\$
1
  • \$\begingroup\$ Good points, both. I really do need to get out the habit of lazily naming my lambda parameters... \$\endgroup\$ May 10, 2015 at 11:02
1
\$\begingroup\$
return compilations
    .SelectMany(x => x.SyntaxTrees.Select(y => new { Compilation = x, SyntaxTree = y }))
    .Select(x => x.Compilation.GetSemanticModel(x.SyntaxTree))
    .SelectMany(
        x => x
            .SyntaxTree
            .GetRoot()
            .DescendantNodes()
            .OfType<InterfaceDeclarationSyntax>()
            .Select(y => x.GetDeclaredSymbol(y)))
    .Where(x => predicate(x.ToDisplayString()))
    .ToImmutableList();

This is the kind of code that would benefit from using LINQ query syntax:

return (from compilation in compilations
        from syntaxTree in compilation.SyntaxTrees
        select compilation.GetSemanticModel(syntaxTree) into semanticModel
        from interfaceDeclaration in semanticModel
            .SyntaxTree
            .GetRoot()
            .DescendantNodes()
            .OfType<InterfaceDeclarationSyntax>()
        select semanticModel.GetDeclaredSymbol(interfaceDeclaration) into symbol
        where predicate(symbol.ToDisplayString())
        select symbol)
    .ToImmutableList();
\$\endgroup\$
2
  • \$\begingroup\$ Why does this benefit from the query syntax? Does this make it faster or is it just easier for you to parse? As far as I know (don't usually use it) the IL code will call all the same methods.... \$\endgroup\$ Mar 5 at 17:30
  • \$\begingroup\$ @user3797758 You're right, it's not faster, but I find it easier to read. \$\endgroup\$
    – svick
    Mar 24 at 9:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.