符号信息获取详解
📚 文档导航
本文档详细介绍如何使用语义模型获取和处理符号信息。
📖 文档系列
| 文档 | 内容 | 难度 |
|---|---|---|
| 语义模型基础 | 基础概念、快速入门、核心功能 | 🟢 入门 |
| 类型信息获取 | 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;
}下一步
🔗 相关资源
- 语义模型基础 - 返回主文档
- 符号系统 - 深入了解符号系统
- ISymbol API(官方文档)
最后更新: 2025-01-21