使用 Roslyn 从编译中获取所有类型?

Get all types from compilation using Roslyn?

我正在尝试通过以下代码创建的 Roslyn 编译中的某些标准获取所有类型:

var syntaxTrees = new[] 
{ 
      CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Preview)) 
};

// Add some references
references.Add(MetadataReference.CreateFromFile(...));
            
// Create compilation
var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
var compilation = CSharpCompilation.Create(nameof(GeneratorRunner), syntaxTrees, references, options);

return compilation;

然后我使用这段代码来搜索类型

compilation.GetTypeByMetadataName("HomeCenter.Messages.Commands.Device.AdjustPowerLevelCommand");
compilation.GetSymbolsWithName(x => true, SymbolFilter.Type).ToList();

这对我来说很奇怪 - GetSymbolsWithName returns 仅在我的源代码中定义的类型,而不是在引用的程序集中定义的类型,但 GetTypeByMetadataName 能够 return 有关类型的信息,即使它是在引用程序集中定义的.问题是我试图在编译中搜索所有类型,所以我不知道确切的名称。问题是我如何在所有类型的应用程序中搜索源代码以及引用的程序集?还有与名称相反的任何其他过滤选项 - 我对从特定类型继承的所有类型感兴趣,因此可以以各种方式命名它们。

如果要开始搜索所有类型,可以执行 Compilation.GlobalNamespace,这会为您提供一个 INamespaceSymbol,它表示层次结构的“根”命名空间。从那里您可以调用 GetTypeMembers() 来获取该命名空间中的类型,并调用 GetNamespaceMembers() 来获取子命名空间。您必须自己进行递归,我认为我们没有这方面的帮助程序。

只是对 Jason Malinowski 的回答的补充。您可以使用以下符号访客助手:

internal class ExportedTypesCollector : SymbolVisitor
{
    private readonly CancellationToken _cancellationToken;
    private readonly HashSet<INamedTypeSymbol> _exportedTypes;

    public ExportedTypesCollector(CancellationToken cancellation, int? estimatedCapacity = null)
    { 
        _cancellationToken = cancellation;
        _exportedTypes = estimatedCapacity.HasValue
            ? new HashSet<INamedTypeSymbol>(estimatedCapacity.Value, SymbolEqualityComparer.Default)
            : new HashSet<INamedTypeSymbol>(SymbolEqualityComparer.Default);
    }

    public ImmutableArray<INamedTypeSymbol> GetPublicTypes() => _exportedTypes.ToImmutableArray();

    public override void VisitAssembly(IAssemblySymbol symbol)
    {
        _cancellationToken.ThrowIfCancellationRequested();
        symbol.GlobalNamespace.Accept(this);
    }

    public override void VisitNamespace(INamespaceSymbol symbol)
    {
        foreach (INamespaceOrTypeSymbol namespaceOrType in symbol.GetMembers())
        {
            _cancellationToken.ThrowIfCancellationRequested();
            namespaceOrType.Accept(this);
        }
    }

    public override void VisitNamedType(INamedTypeSymbol type)
    {
        _cancellationToken.ThrowIfCancellationRequested();

        if (!type.IsAccessibleOutsideOfAssembly() || !_exportedTypes.Add(type))
            return;

        var nestedTypes = type.GetTypeMembers();

        if (nestedTypes.IsDefaultOrEmpty)
            return;

        foreach (INamedTypeSymbol nestedType in nestedTypes)
        {
            _cancellationToken.ThrowIfCancellationRequested();
            nestedType.Accept(this);
        }
    }
}

使用这样的扩展方法 IsAccessibleOutsideOfAssembly

public static bool IsAccessibleOutsideOfAssembly(this ISymbol symbol) =>
        symbol.DeclaredAccessibility switch
        {
            Accessibility.Private => false,
            Accessibility.Internal => false,
            Accessibility.ProtectedAndInternal => false,
            Accessibility.Protected => true,
            Accessibility.ProtectedOrInternal => true,
            Accessibility.Public => true,
            _ => true,    //Here should be some reasonable default
        };