提问人:user3797758 提问时间:3/15/2023 更新时间:3/16/2023 访问量:751
源生成器 - 获取符号的所有实现(包括依赖程序集)的方法?
Source generator - method of getting all implementations of a symbol (including dependent assemblies)?
问:
我有一个增量源生成器,需要找到给定符号(暂时的接口)的所有实现。通过一些谷歌搜索,我发现了这个问题,它建议Microsoft.CodeAnalysis.FindSymbols.SymbolFinder.FindImplementedInterfaceMembers
,它完全符合我的需求,但它适用于独立的Roslyn,没有代码生成器部分,这意味着我没有满足参数所需的结构。
有没有办法使用源生成器基础设施获取接口(或任意符号)的所有实现?
我发现的主要问题是我还想了解依赖项中的实现,这意味着我也需要从类中提取数据。Compilation
答:
-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 中使用的所有程序集,因为这两种类型中的所有内容都无法知道,因为程序集中的循环引用是不允许的,这留下了所有可能具有实现的程序集。T
T
T
这将大大减少必须检查的类型总数,使此代码更快,但我目前尚未进行此优化。
评论