源代码生成器 - 获取符号的所有实现的方法(包括依赖程序集)?

问题描述 投票:0回答:1

我有一个增量源生成器,需要找到给定符号的所有实现(暂时是接口)。通过一些谷歌搜索我发现了这个问题,它建议

Microsoft.CodeAnalysis.FindSymbols.SymbolFinder.FindImplementedInterfaceMembers
这正是我想要的,但它是独立的 Roslyn,没有代码生成器部分,这意味着我没有实现参数所需的结构。

有没有一种方法可以使用源生成器基础设施获取接口(或任意符号)的所有实现?

我发现的主要问题是我还想了解依赖项中的实现,这意味着我也需要从

Compilation
类中提取数据。

c# roslyn-code-analysis sourcegenerators
1个回答
0
投票

这是一个功能性解决方案,可以通过更多的工作进行优化

public static ImmutableArray<INamedTypeSymbol> FindImplementations<T>(Compilation compilation)
    where T : class
{
    // This chunk of code is pretty inefficient and could be greatly improved!!
    INamedTypeSymbol[] allTypes = GetAllNamespaces(compilation)
        .SelectMany(t => t.GetTypeMembers())
        .SelectMany(AllNestedTypesAndSelf)
        .ToArray();

    string[] targetNamespace = typeof(T).Namespace.Split('.');
    string targetName = typeof(T).Name;
    INamedTypeSymbol? desiredType = allTypes
        .FirstOrDefault(p => MatchesBaseType(p, targetNamespace, targetName));

    if (desiredType == null)
    {
        // we wouldn't find the type anyway
        return ImmutableArray<INamedTypeSymbol>.Empty;
    }

    // search for the applicable types
    if (typeof(T).IsInterface)
    {
         return allTypes
            .Where(t => t.AllInterfaces.Contains(desiredType, SymbolEqualityComparer.Default))
            .ToImmutableArray();
    }
    else if (typeof(T).IsClass)
    {
        return allTypes
            .Where(MatchesBaseType)
            .ToImmutableArray();
    }

    // theoretically impossible because of T restrictions
    throw new InvalidOperationException("Unexpected implementation result!");
}

private static IEnumerable<INamedTypeSymbol> AllNestedTypesAndSelf(this INamedTypeSymbol type)
{
    yield return type;
    foreach (var typeMember in type.GetTypeMembers())
    {
        foreach (var nestedType in typeMember.AllNestedTypesAndSelf())
        {
                yield return nestedType;
        }
    }
}

private static ImmutableArray<INamespaceSymbol> GetAllNamespaces(Compilation compilation)
{
    HashSet<INamespaceSymbol> seen = new HashSet<INamespaceSymbol>(SymbolEqualityComparer.Default);
    Queue<INamespaceSymbol> visit = new Queue<INamespaceSymbol>();
    visit.Enqueue(compilation.GlobalNamespace);

    do
    {
        INamespaceSymbol search = visit.Dequeue();
        seen.Add(search);

        foreach (INamespaceSymbol? space in search.GetNamespaceMembers())
        {
            if (space == null || seen.Contains(space))
            {
                continue;
            }

            visit.Enqueue(space);
        }
    } while (visit.Count > 0);

    return seen.ToImmutableArray();
}

private static bool MatchesBaseType(INamedTypeSymbol symbol)
{
    return symbol.BaseType != null && 
        (SymbolEqualityComparer.Default.Equals(symbol.BaseType, desiredType) 
        || checkBaseType(symbol.BaseType));
}

private static bool IsTypeMatch(INamedTypeSymbol symbol, string[] searchNamespace, string searchName)
{
    if (symbol.Name != searchName)
    {
        return false;
    }

    INamespaceSymbol? currentNamespace = symbol.ContainingNamespace;
    for (int i = searchNamespace.Length - 1; i >= 0; i--)
    {
        if (searchNamespace[i] != currentNamespace?.Name)
        {
            return false;
        }

        currentNamespace = currentNamespace.ContainingNamespace;
    }

    // this should be the global namespace to indicate that we have
    // reached the root of the namespace
    return currentNamespace?.IsGlobalNamespace ?? false;
}

后期可以做的优化

我们知道

T
存在于哪个程序集中,我们知道我们正在编译什么,所以我们应该能够丢弃所有在编译和
T
-程序集中使用的程序集,因为这两种类型中的所有内容都不知道
T
由于程序集中的循环引用是不允许的,因此所有程序集都可能有一个实现。

这将大大减少必须检查的类型总数,使这段代码更快,但我目前还没有进行这种优化。

© www.soinside.com 2019 - 2024. All rights reserved.