You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 10:12:55