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

Source generator - method of getting all implementations of a symbol (including dependent assemblies)?

提问人:user3797758 提问时间:3/15/2023 更新时间:3/16/2023 访问量:751

问:

我有一个增量源生成器,需要找到给定符号(暂时的接口)的所有实现。通过一些谷歌搜索,我发现了这个问题,它建议Microsoft.CodeAnalysis.FindSymbols.SymbolFinder.FindImplementedInterfaceMembers,它完全符合我的需求,但它适用于独立的Roslyn,没有代码生成器部分,这意味着我没有满足参数所需的结构。

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

我发现的主要问题是我还想了解依赖项中的实现,这意味着我也需要从类中提取数据。Compilation

C# roslyn-code-analysis 源生成器

评论


答:

-1赞 user3797758 #1

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

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;
}

可以稍后完成的优化

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

这将大大减少必须检查的类型总数,使此代码更快,但我目前尚未进行此优化。