Skip to content

符号信息获取详解

📚 文档导航

本文档详细介绍如何使用语义模型获取和处理符号信息。

📖 文档系列

文档内容难度
语义模型基础基础概念、快速入门、核心功能🟢 入门
类型信息获取GetTypeInfo、常量值、TypeInfo 详解🟡 中级
符号信息获取GetDeclaredSymbol、GetSymbolInfo、符号遍历🟡 中级
高级主题泛型、继承、特性、类型转换🔴 高级
最佳实践性能优化、常见问题、实战场景🟡 中级

GetDeclaredSymbol 方法

GetDeclaredSymbol 用于获取声明节点对应的符号。

💡 核心概念

  • 声明节点:定义类、方法、属性等的语法节点
  • 符号(Symbol):代表程序元素的语义信息

完整示例

csharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

public class DeclaredSymbolExamples
{
    public void DemonstrateGetDeclaredSymbol()
    {
        var code = @"
            namespace MyApp
            {
                public class Person
                {
                    private string _name;
                    
                    public string Name 
                    { 
                        get => _name;
                        set => _name = value;
                    }
                    
                    public int Age { get; set; }
                    
                    public Person(string name, int age)
                    {
                        Name = name;
                        Age = age;
                    }
                    
                    public void SayHello()
                    {
                        Console.WriteLine($""Hello, I'm {Name}"");
                    }
                }
            }
        ";
        
        var tree = CSharpSyntaxTree.ParseText(code);
        var compilation = CreateCompilation(tree);
        var model = compilation.GetSemanticModel(tree);
        var root = tree.GetRoot();
        
        // 1. 获取类符号
        var classDecl = root.DescendantNodes()
            .OfType<ClassDeclarationSyntax>()
            .First();
        
        var classSymbol = model.GetDeclaredSymbol(classDecl) as INamedTypeSymbol;
        
        Console.WriteLine($"类符号: {classSymbol.ToDisplayString()}");
        Console.WriteLine($"  命名空间: {classSymbol.ContainingNamespace.ToDisplayString()}");
        Console.WriteLine($"  访问级别: {classSymbol.DeclaredAccessibility}");
        Console.WriteLine($"  类型种类: {classSymbol.TypeKind}");
        Console.WriteLine($"  是否抽象: {classSymbol.IsAbstract}");
        Console.WriteLine($"  是否密封: {classSymbol.IsSealed}");
        Console.WriteLine();
        
        // 2. 获取字段符号
        var fields = root.DescendantNodes()
            .OfType<FieldDeclarationSyntax>();
        
        foreach (var field in fields)
        {
            var variable = field.Declaration.Variables.First();
            var fieldSymbol = model.GetDeclaredSymbol(variable) as IFieldSymbol;
            
            Console.WriteLine($"字段符号: {fieldSymbol.Name}");
            Console.WriteLine($"  类型: {fieldSymbol.Type.ToDisplayString()}");
            Console.WriteLine($"  访问级别: {fieldSymbol.DeclaredAccessibility}");
            Console.WriteLine($"  是否只读: {fieldSymbol.IsReadOnly}");
            Console.WriteLine($"  是否静态: {fieldSymbol.IsStatic}");
            Console.WriteLine();
        }
        
        // 3. 获取属性符号
        var properties = root.DescendantNodes()
            .OfType<PropertyDeclarationSyntax>();
        
        foreach (var property in properties)
        {
            var propertySymbol = model.GetDeclaredSymbol(property) as IPropertySymbol;
            
            Console.WriteLine($"属性符号: {propertySymbol.Name}");
            Console.WriteLine($"  类型: {propertySymbol.Type.ToDisplayString()}");
            Console.WriteLine($"  访问级别: {propertySymbol.DeclaredAccessibility}");
            Console.WriteLine($"  有 getter: {propertySymbol.GetMethod != null}");
            Console.WriteLine($"  有 setter: {propertySymbol.SetMethod != null}");
            Console.WriteLine($"  是否自动属性: {propertySymbol.IsAutoProperty()}");
            Console.WriteLine();
        }
        
        // 4. 获取方法符号
        var methods = root.DescendantNodes()
            .OfType<MethodDeclarationSyntax>();
        
        foreach (var method in methods)
        {
            var methodSymbol = model.GetDeclaredSymbol(method) as IMethodSymbol;
            
            Console.WriteLine($"方法符号: {methodSymbol.Name}");
            Console.WriteLine($"  返回类型: {methodSymbol.ReturnType.ToDisplayString()}");
            Console.WriteLine($"  访问级别: {methodSymbol.DeclaredAccessibility}");
            Console.WriteLine($"  是否静态: {methodSymbol.IsStatic}");
            Console.WriteLine($"  是否虚方法: {methodSymbol.IsVirtual}");
            Console.WriteLine($"  是否异步: {methodSymbol.IsAsync}");
            Console.WriteLine($"  参数数量: {methodSymbol.Parameters.Length}");
            Console.WriteLine();
        }
        
        // 5. 获取构造函数符号
        var constructors = root.DescendantNodes()
            .OfType<ConstructorDeclarationSyntax>();
        
        foreach (var constructor in constructors)
        {
            var ctorSymbol = model.GetDeclaredSymbol(constructor) as IMethodSymbol;
            
            Console.WriteLine($"构造函数符号: {ctorSymbol.ToDisplayString()}");
            Console.WriteLine($"  参数:");
            foreach (var param in ctorSymbol.Parameters)
            {
                Console.WriteLine($"    - {param.Type.ToDisplayString()} {param.Name}");
            }
            Console.WriteLine();
        }
    }
    
    private Compilation CreateCompilation(SyntaxTree tree)
    {
        return CSharpCompilation.Create("temp")
            .AddReferences(
                MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
                MetadataReference.CreateFromFile(typeof(Console).Assembly.Location)
            )
            .AddSyntaxTrees(tree);
    }
}

// 扩展方法:检查是否是自动属性
public static class PropertySymbolExtensions
{
    public static bool IsAutoProperty(this IPropertySymbol property)
    {
        // 自动属性的 getter 和 setter 都没有显式实现
        var getterHasBody = property.GetMethod?.DeclaringSyntaxReferences
            .Select(r => r.GetSyntax())
            .OfType<AccessorDeclarationSyntax>()
            .Any(a => a.Body != null || a.ExpressionBody != null) == true;
        
        var setterHasBody = property.SetMethod?.DeclaringSyntaxReferences
            .Select(r => r.GetSyntax())
            .OfType<AccessorDeclarationSyntax>()
            .Any(a => a.Body != null || a.ExpressionBody != null) == true;
        
        return !getterHasBody && !setterHasBody;
    }
}

GetSymbolInfo 方法

GetSymbolInfo 用于获取表达式引用的符号。

💡 区别

  • GetDeclaredSymbol: 获取声明的符号(定义)
  • GetSymbolInfo: 获取引用的符号(使用)

基础用法

csharp
public class SymbolInfoExamples
{
    public void DemonstrateGetSymbolInfo()
    {
        var code = @"
            class Calculator
            {
                private int _value;
                
                public int Value 
                { 
                    get => _value;
                    set => _value = value;
                }
                
                public int Add(int a, int b)
                {
                    return a + b;
                }
                
                public void Test()
                {
                    var x = 5;
                    var y = 10;
                    var sum = Add(x, y);
                    
                    Value = sum;
                    var current = Value;
                }
            }
        ";
        
        var tree = CSharpSyntaxTree.ParseText(code);
        var compilation = CreateCompilation(tree);
        var model = compilation.GetSemanticModel(tree);
        var root = tree.GetRoot();
        
        // 1. 获取标识符引用的符号
        var identifiers = root.DescendantNodes()
            .OfType<IdentifierNameSyntax>()
            .Where(i => i.Parent is not MemberAccessExpressionSyntax ||
                       ((MemberAccessExpressionSyntax)i.Parent).Name == i);
        
        foreach (var identifier in identifiers)
        {
            var symbolInfo = model.GetSymbolInfo(identifier);
            var symbol = symbolInfo.Symbol;
            
            if (symbol != null)
            {
                Console.WriteLine($"标识符: {identifier.Identifier.Text}");
                Console.WriteLine($"  符号种类: {symbol.Kind}");
                Console.WriteLine($"  完整名称: {symbol.ToDisplayString()}");
                Console.WriteLine($"  定义位置: {symbol.Locations.FirstOrDefault()?.GetLineSpan()}");
                Console.WriteLine();
            }
        }
        
        // 2. 获取方法调用的符号
        var invocations = root.DescendantNodes()
            .OfType<InvocationExpressionSyntax>();
        
        foreach (var invocation in invocations)
        {
            var symbolInfo = model.GetSymbolInfo(invocation);
            var methodSymbol = symbolInfo.Symbol as IMethodSymbol;
            
            if (methodSymbol != null)
            {
                Console.WriteLine($"方法调用: {invocation.Expression}");
                Console.WriteLine($"  方法签名: {methodSymbol.ToDisplayString()}");
                Console.WriteLine($"  返回类型: {methodSymbol.ReturnType.ToDisplayString()}");
                Console.WriteLine($"  参数:");
                foreach (var param in methodSymbol.Parameters)
                {
                    Console.WriteLine($"    - {param.Type.ToDisplayString()} {param.Name}");
                }
                Console.WriteLine();
            }
        }
    }
    
    private Compilation CreateCompilation(SyntaxTree tree)
    {
        return CSharpCompilation.Create("temp")
            .AddReferences(MetadataReference.CreateFromFile(typeof(object).Assembly.Location))
            .AddSyntaxTrees(tree);
    }
}

处理重载解析

当方法有多个重载时,GetSymbolInfo 可以帮助确定调用的是哪个重载。

csharp
public class OverloadResolution
{
    public void DemonstrateOverloads()
    {
        var code = @"
            class Overloads
            {
                public void Method(int x) { }
                public void Method(string x) { }
                
                public void Test()
                {
                    Method(5);  // 明确:调用 Method(int)
                    Method(""hello"");  // 明确:调用 Method(string)
                }
            }
        ";
        
        var tree = CSharpSyntaxTree.ParseText(code);
        var compilation = CreateCompilation(tree);
        var model = compilation.GetSemanticModel(tree);
        var root = tree.GetRoot();
        
        var invocations = root.DescendantNodes()
            .OfType<InvocationExpressionSyntax>();
        
        foreach (var invocation in invocations)
        {
            var symbolInfo = model.GetSymbolInfo(invocation);
            
            Console.WriteLine($"方法调用: {invocation}");
            Console.WriteLine($"  解析的符号: {symbolInfo.Symbol?.ToDisplayString() ?? "无"}");
            Console.WriteLine($"  候选符号数量: {symbolInfo.CandidateSymbols.Length}");
            
            if (symbolInfo.CandidateSymbols.Any())
            {
                Console.WriteLine($"  候选符号:");
                foreach (var candidate in symbolInfo.CandidateSymbols)
                {
                    Console.WriteLine($"    - {candidate.ToDisplayString()}");
                }
            }
            
            Console.WriteLine($"  候选原因: {symbolInfo.CandidateReason}");
            Console.WriteLine();
        }
    }
    
    private Compilation CreateCompilation(SyntaxTree tree)
    {
        return CSharpCompilation.Create("temp")
            .AddReferences(MetadataReference.CreateFromFile(typeof(object).Assembly.Location))
            .AddSyntaxTrees(tree);
    }
}

💡 SymbolInfo 属性

  • Symbol: 成功解析的符号
  • CandidateSymbols: 候选符号列表(当解析失败时)
  • CandidateReason: 解析失败的原因

符号遍历

遍历类的所有成员

csharp
public class SymbolTraversal
{
    public void TraverseMembers(INamedTypeSymbol typeSymbol)
    {
        Console.WriteLine($"类: {typeSymbol.Name}");
        Console.WriteLine($"\n所有成员:");
        
        foreach (var member in typeSymbol.GetMembers())
        {
            Console.WriteLine($"  {member.Kind}: {member.ToDisplayString()}");
        }
        
        Console.WriteLine($"\n字段:");
        foreach (var field in typeSymbol.GetMembers().OfType<IFieldSymbol>())
        {
            Console.WriteLine($"  - {field.Name}: {field.Type.ToDisplayString()}");
        }
        
        Console.WriteLine($"\n属性:");
        foreach (var property in typeSymbol.GetMembers().OfType<IPropertySymbol>())
        {
            Console.WriteLine($"  - {property.Name}: {property.Type.ToDisplayString()}");
        }
        
        Console.WriteLine($"\n方法:");
        foreach (var method in typeSymbol.GetMembers().OfType<IMethodSymbol>())
        {
            if (method.MethodKind == MethodKind.Ordinary)
            {
                Console.WriteLine($"  - {method.Name}({string.Join(", ", method.Parameters.Select(p => p.Type.ToDisplayString()))})");
            }
        }
        
        Console.WriteLine($"\n构造函数:");
        foreach (var ctor in typeSymbol.Constructors)
        {
            Console.WriteLine($"  - {ctor.ToDisplayString()}");
        }
    }
}

按名称查找成员

csharp
public class MemberLookup
{
    // 查找特定名称的成员
    public IEnumerable<ISymbol> FindMembersByName(INamedTypeSymbol typeSymbol, string name)
    {
        return typeSymbol.GetMembers(name);
    }
    
    // 查找特定名称的方法
    public IEnumerable<IMethodSymbol> FindMethodsByName(INamedTypeSymbol typeSymbol, string name)
    {
        return typeSymbol.GetMembers(name).OfType<IMethodSymbol>();
    }
    
    // 查找特定名称的属性
    public IPropertySymbol FindProperty(INamedTypeSymbol typeSymbol, string name)
    {
        return typeSymbol.GetMembers(name).OfType<IPropertySymbol>().FirstOrDefault();
    }
    
    // 查找特定名称的字段
    public IFieldSymbol FindField(INamedTypeSymbol typeSymbol, string name)
    {
        return typeSymbol.GetMembers(name).OfType<IFieldSymbol>().FirstOrDefault();
    }
}

符号比较

使用 SymbolEqualityComparer

csharp
public class SymbolComparison
{
    public void CompareSymbols(ISymbol symbol1, ISymbol symbol2)
    {
        // ✅ 正确的比较方式
        bool areEqual = SymbolEqualityComparer.Default.Equals(symbol1, symbol2);
        
        // ❌ 错误的比较方式
        // bool areEqual = symbol1 == symbol2;  // 不要这样做!
        // bool areEqual = symbol1.Equals(symbol2);  // 不要这样做!
        
        Console.WriteLine($"符号相等: {areEqual}");
    }
    
    // 检查符号是否在集合中
    public bool IsSymbolInSet(ISymbol symbol, HashSet<ISymbol> symbolSet)
    {
        // 使用 SymbolEqualityComparer 创建 HashSet
        var set = new HashSet<ISymbol>(SymbolEqualityComparer.Default);
        set.UnionWith(symbolSet);
        
        return set.Contains(symbol);
    }
}

⚠️ 重要

永远使用 SymbolEqualityComparer.Default 来比较符号,不要使用 ==Equals()


实用工具方法

符号检查工具

csharp
public static class SymbolHelpers
{
    // 检查符号是否是公共的
    public static bool IsPublic(ISymbol symbol)
    {
        return symbol.DeclaredAccessibility == Accessibility.Public;
    }
    
    // 检查符号是否是静态的
    public static bool IsStatic(ISymbol symbol)
    {
        return symbol.IsStatic;
    }
    
    // 检查符号是否是抽象的
    public static bool IsAbstract(ISymbol symbol)
    {
        return symbol.IsAbstract;
    }
    
    // 检查符号是否是虚方法
    public static bool IsVirtual(ISymbol symbol)
    {
        return symbol.IsVirtual;
    }
    
    // 检查符号是否是密封的
    public static bool IsSealed(ISymbol symbol)
    {
        return symbol.IsSealed;
    }
    
    // 获取符号的完整名称
    public static string GetFullName(ISymbol symbol)
    {
        return symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
    }
    
    // 获取符号的简短名称
    public static string GetShortName(ISymbol symbol)
    {
        return symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
    }
}

方法符号工具

csharp
public static class MethodSymbolHelpers
{
    // 检查方法是否是扩展方法
    public static bool IsExtensionMethod(IMethodSymbol method)
    {
        return method.IsExtensionMethod;
    }
    
    // 检查方法是否是异步方法
    public static bool IsAsync(IMethodSymbol method)
    {
        return method.IsAsync;
    }
    
    // 获取方法的参数类型
    public static ITypeSymbol[] GetParameterTypes(IMethodSymbol method)
    {
        return method.Parameters.Select(p => p.Type).ToArray();
    }
    
    // 检查方法是否有特定参数类型
    public static bool HasParameterOfType(IMethodSymbol method, ITypeSymbol type)
    {
        return method.Parameters.Any(p => 
            SymbolEqualityComparer.Default.Equals(p.Type, type));
    }
    
    // 获取方法签名
    public static string GetSignature(IMethodSymbol method)
    {
        var parameters = string.Join(", ", 
            method.Parameters.Select(p => $"{p.Type.ToDisplayString()} {p.Name}"));
        
        return $"{method.ReturnType.ToDisplayString()} {method.Name}({parameters})";
    }
}

常见场景

场景 1: 查找类的所有公共属性

csharp
public IEnumerable<IPropertySymbol> GetPublicProperties(INamedTypeSymbol typeSymbol)
{
    return typeSymbol.GetMembers()
        .OfType<IPropertySymbol>()
        .Where(p => p.DeclaredAccessibility == Accessibility.Public);
}

场景 2: 查找类的所有构造函数

csharp
public IEnumerable<IMethodSymbol> GetConstructors(INamedTypeSymbol typeSymbol)
{
    return typeSymbol.Constructors;
}

场景 3: 检查方法是否重写了基类方法

csharp
public bool IsOverride(IMethodSymbol method)
{
    return method.IsOverride;
}

public IMethodSymbol GetOverriddenMethod(IMethodSymbol method)
{
    return method.OverriddenMethod;
}

场景 4: 获取符号的定义位置

csharp
public Location GetDefinitionLocation(ISymbol symbol)
{
    return symbol.Locations.FirstOrDefault();
}

public string GetDefinitionFilePath(ISymbol symbol)
{
    var location = symbol.Locations.FirstOrDefault();
    return location?.SourceTree?.FilePath;
}

下一步


🔗 相关资源


最后更新: 2025-01-21

基于 MIT 许可发布