基于Roslyn查找解决方案及引用程序集中的派生接口符号
高效查找继承特定接口的所有接口符号(含引用程序集)
好问题!我之前也碰到过类似的需求,Roslyn的SymbolFinder系列方法确实在处理接口继承时有点“重类轻接口”——你尝试的几个方法要么只返回类,要么范围受限,确实不够用。下面给你两个层级的解决方案,兼顾简洁性和性能:
一、解决方案内的接口:用SymbolFinder.FindDerivedSymbolsAsync(内置高效方法)
你可能没注意到,SymbolFinder其实有个FindDerivedSymbolsAsync方法,它和FindDerivedClassesAsync的核心区别是会返回所有派生的符号,包括接口!而且可以通过参数过滤掉类,只保留接口:
var derivedInterfacesInSolution = await SymbolFinder.FindDerivedSymbolsAsync( targetInterfaceSymbol, solution, includeImplementationTypes: false // 设为false,只返回派生接口,排除实现类 );
这个方法会利用Roslyn的内置缓存,比自己手动遍历要高效得多,代码也非常简洁,完美覆盖你需要的解决方案内接口范围。
二、引用程序集中的接口:结合程序集遍历+符号检查
遗憾的是,SymbolFinder的方法目前都不支持直接扫描引用程序集,这部分确实需要手动处理,但可以通过Roslyn的符号API优化遍历逻辑,避免低效的全量递归:
步骤1:收集所有需要扫描的引用程序集符号
首先,从解决方案的所有项目编译中,收集所有唯一的引用程序集:
var allCompilations = await Task.WhenAll(solution.Projects.Select(p => p.GetCompilationAsync())); var referencedAssemblies = new HashSet<IAssemblySymbol>(SymbolEqualityComparer.Default); foreach (var compilation in allCompilations) { foreach (var reference in compilation.References) { var assemblySymbol = compilation.GetAssemblyOrModuleSymbol(reference) as IAssemblySymbol; if (assemblySymbol != null && !assemblySymbol.IsDynamic) { referencedAssemblies.Add(assemblySymbol); } } }
步骤2:高效扫描程序集内的接口
这里推荐用SymbolVisitor的变种,专门收集符合条件的接口——相比全量遍历,它只会处理类型符号,减少不必要的开销:
class DerivedInterfaceVisitor : SymbolVisitor { private readonly INamedTypeSymbol _targetInterface; private readonly List<INamedTypeSymbol> _derivedInterfaces = new(); private readonly SymbolEqualityComparer _equalityComparer = SymbolEqualityComparer.Default; public DerivedInterfaceVisitor(INamedTypeSymbol targetInterface) { _targetInterface = targetInterface.OriginalDefinition; // 用原始定义处理泛型等重载情况 } public IReadOnlyList<INamedTypeSymbol> DerivedInterfaces => _derivedInterfaces; public override void VisitNamedType(INamedTypeSymbol symbol) { // 只处理:是接口、不是目标接口本身、继承了目标接口 if (symbol.TypeKind == TypeKind.Interface && !_equalityComparer.Equals(symbol.OriginalDefinition, _targetInterface) && symbol.AllInterfaces.Any(i => _equalityComparer.Equals(i.OriginalDefinition, _targetInterface))) { _derivedInterfaces.Add(symbol); } // 递归遍历嵌套类型 base.VisitNamedType(symbol); } }
然后用这个访问器扫描每个引用程序集:
var allDerivedInterfaces = new List<INamedTypeSymbol>(derivedInterfacesInSolution); foreach (var assembly in referencedAssemblies) { var visitor = new DerivedInterfaceVisitor(targetInterfaceSymbol); visitor.Visit(assembly.GlobalNamespace); allDerivedInterfaces.AddRange(visitor.DerivedInterfaces); }
关键性能优化点
- 使用
SymbolEqualityComparer.Default比较符号,避免引用相等的坑(比如不同编译上下文的同一符号) - 用
OriginalDefinition处理泛型接口的实例(比如IMyInterface<int>和IMyInterface<string>的原始定义是同一个接口) - 过滤掉动态程序集,减少不必要的扫描
三、合并方案:一站式获取所有接口
把上面两部分结合起来,就能得到解决方案+引用程序集中所有继承目标接口的接口符号了。这个方案既利用了Roslyn内置的高效工具处理解决方案内的部分,又通过优化的符号遍历处理引用程序集,比全手动遍历要高效得多,代码也更简洁稳定。
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

