8
\$\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\$
  • 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 '15 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\$ – Kent Boogaart Mar 25 '15 at 2:59
  • \$\begingroup\$ Very strange. Yes, targeting .NET 4.5 -- I've put the code up here. \$\endgroup\$ – mjolka Mar 25 '15 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 '15 at 23:10
3
\$\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\$
  • \$\begingroup\$ Good points, both. I really do need to get out the habit of lazily naming my lambda parameters... \$\endgroup\$ – Kent Boogaart May 10 '15 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\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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