语义模型基础
⏱️ 10-15 分钟 | 📚 基础级别 | 前置知识:编译基础
🎯 学习目标
完成本文档后,你将能够:
- [ ] 理解语义模型与语法树的区别
- [ ] 获取和使用语义模型
- [ ] 获取符号的类型信息
- [ ] 查找和分析类、方法、属性等符号
- [ ] 处理特性(Attribute)信息
📖 核心概念
什么是语义模型?
语义模型(Semantic Model) 提供代码的语义信息,包括类型、符号、作用域等。与语法树只关注代码结构不同,语义模型关注代码的含义。
语法树 vs 语义模型:
| 特性 | 语法树 | 语义模型 |
|---|---|---|
| 关注点 | 代码结构 | 代码含义 |
| 提供信息 | 类名、方法名、语法结构 | 类型、符号、作用域、重载解析 |
| 示例 | ClassDeclarationSyntax | INamedTypeSymbol |
| 使用场景 | 查找语法节点、代码格式化 | 类型检查、符号查找、语义分析 |
简单理解:
- 语法树告诉你"这是一个类声明"
- 语义模型告诉你"这个类叫什么名字、继承自哪个类、有哪些成员"
核心概念
| 概念 | 说明 | 示例 |
|---|---|---|
| SemanticModel | 提供语义信息的模型 | compilation.GetSemanticModel(tree) |
| ISymbol | 代码元素的语义表示 | 类、方法、属性等 |
| ITypeSymbol | 类型的语义表示 | int, string, MyClass |
| Accessibility | 访问修饰符 | Public, Private, Internal |
| SymbolKind | 符号类型 | NamedType, Method, Property |
🔧 常用方法
方法 1:获取语义模型
用途:从编译对象获取语义模型
使用场景:需要分析代码语义时的第一步
csharp
// 从编译获取语义模型
Compilation compilation = /* ... */;
SyntaxTree syntaxTree = /* ... */;
SemanticModel semanticModel = compilation.GetSemanticModel(syntaxTree);在增量生成器中使用:
csharp
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var provider = context.SyntaxProvider.CreateSyntaxProvider(
predicate: static (node, _) => node is ClassDeclarationSyntax,
transform: static (ctx, _) => {
// ctx.SemanticModel 就是语义模型
var semanticModel = ctx.SemanticModel;
var classSymbol = semanticModel.GetDeclaredSymbol(ctx.Node);
return classSymbol;
});
}方法 2:获取声明的符号
用途:从声明节点获取符号信息
使用场景:分析类、方法、属性等声明
csharp
// 获取类符号
var classDecl = (ClassDeclarationSyntax)node;
var classSymbol = semanticModel.GetDeclaredSymbol(classDecl) as INamedTypeSymbol;
// 获取方法符号
var methodDecl = (MethodDeclarationSyntax)node;
var methodSymbol = semanticModel.GetDeclaredSymbol(methodDecl) as IMethodSymbol;
// 获取属性符号
var propertyDecl = (PropertyDeclarationSyntax)node;
var propertySymbol = semanticModel.GetDeclaredSymbol(propertyDecl) as IPropertySymbol;方法 3:获取类型信息
用途:获取表达式的类型
使用场景:需要知道变量、表达式的类型时
csharp
// 获取变量类型
// var x = 42;
var variableDecl = (VariableDeclaratorSyntax)node;
var typeInfo = semanticModel.GetTypeInfo(variableDecl.Initializer.Value);
// typeInfo.Type = System.Int32
// 获取表达式类型
TypeInfo typeInfo = semanticModel.GetTypeInfo(expression);
ITypeSymbol type = typeInfo.Type; // 表达式的类型
ITypeSymbol convertedType = typeInfo.ConvertedType; // 转换后的类型方法 4:获取符号信息(用于引用)
用途:获取表达式引用的符号
使用场景:分析方法调用、变量引用等
csharp
// 获取方法调用的符号
// MyMethod();
var invocation = (InvocationExpressionSyntax)node;
var symbolInfo = semanticModel.GetSymbolInfo(invocation);
var methodSymbol = symbolInfo.Symbol as IMethodSymbol;方法 5:检查特性
用途:检查符号是否有特定特性
使用场景:基于特性的代码生成
csharp
// 检查类是否有特定特性
INamedTypeSymbol classSymbol = /* ... */;
var attributes = classSymbol.GetAttributes();
bool hasAttribute = attributes.Any(a =>
a.AttributeClass?.Name == "MyAttribute" ||
a.AttributeClass?.Name == "MyAttributeAttribute");💡 实践示例
示例 1:分析类的基本信息
场景:提取类的名称、命名空间、访问修饰符等基本信息
csharp
public class ClassInfoExtractor
{
public ClassInfo ExtractClassInfo(
ClassDeclarationSyntax classDecl,
SemanticModel semanticModel)
{
// 1. 获取类符号
var classSymbol = semanticModel.GetDeclaredSymbol(classDecl)
as INamedTypeSymbol;
if (classSymbol == null)
return null;
// 2. 提取基本信息
return new ClassInfo
{
Name = classSymbol.Name,
FullName = classSymbol.ToDisplayString(),
Namespace = classSymbol.ContainingNamespace.ToDisplayString(),
IsAbstract = classSymbol.IsAbstract,
IsSealed = classSymbol.IsSealed,
IsStatic = classSymbol.IsStatic,
Accessibility = classSymbol.DeclaredAccessibility.ToString()
};
}
}
public class ClassInfo
{
public string Name { get; set; }
public string FullName { get; set; }
public string Namespace { get; set; }
public bool IsAbstract { get; set; }
public bool IsSealed { get; set; }
public bool IsStatic { get; set; }
public string Accessibility { get; set; }
}关键点:
- 使用
GetDeclaredSymbol获取类符号 ToDisplayString()获取完全限定名ContainingNamespace获取命名空间- 各种
Is*属性检查修饰符
示例 2:查找类的所有公共方法
场景:分析类的公共方法,用于生成文档或代理类
csharp
public class PublicMethodFinder
{
public List<MethodInfo> FindPublicMethods(
INamedTypeSymbol classSymbol)
{
var methods = new List<MethodInfo>();
// 获取所有成员
foreach (var member in classSymbol.GetMembers())
{
// 只处理公共的普通方法
if (member is IMethodSymbol method &&
method.MethodKind == MethodKind.Ordinary &&
method.DeclaredAccessibility == Accessibility.Public)
{
methods.Add(new MethodInfo
{
Name = method.Name,
ReturnType = method.ReturnType.ToDisplayString(),
Parameters = method.Parameters
.Select(p => $"{p.Type.ToDisplayString()} {p.Name}")
.ToList()
});
}
}
return methods;
}
}
public class MethodInfo
{
public string Name { get; set; }
public string ReturnType { get; set; }
public List<string> Parameters { get; set; }
}关键点:
GetMembers()获取所有成员MethodKind.Ordinary排除构造函数、属性访问器等DeclaredAccessibility检查访问修饰符Parameters获取参数列表
示例 3:检查类是否有特定特性
场景:基于特性的代码生成,只处理带有特定特性的类
csharp
public class AttributeChecker
{
public bool HasGenerateAttribute(
INamedTypeSymbol classSymbol,
string attributeName)
{
// 获取所有特性
var attributes = classSymbol.GetAttributes();
// 检查是否有指定特性(支持带或不带 "Attribute" 后缀)
return attributes.Any(a =>
a.AttributeClass?.Name == attributeName ||
a.AttributeClass?.Name == attributeName + "Attribute");
}
public AttributeInfo GetAttributeInfo(
INamedTypeSymbol classSymbol,
string attributeName)
{
// 获取特定特性
var attribute = classSymbol.GetAttributes()
.FirstOrDefault(a =>
a.AttributeClass?.Name == attributeName ||
a.AttributeClass?.Name == attributeName + "Attribute");
if (attribute == null)
return null;
// 提取构造函数参数
var ctorArgs = attribute.ConstructorArguments
.Select(arg => arg.Value)
.ToList();
// 提取命名参数
var namedArgs = attribute.NamedArguments
.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value.Value);
return new AttributeInfo
{
Name = attribute.AttributeClass.Name,
ConstructorArguments = ctorArgs,
NamedArguments = namedArgs
};
}
}
public class AttributeInfo
{
public string Name { get; set; }
public List<object> ConstructorArguments { get; set; }
public Dictionary<string, object> NamedArguments { get; set; }
}关键点:
GetAttributes()获取所有特性- 支持带或不带 "Attribute" 后缀的名称
ConstructorArguments获取构造函数参数NamedArguments获取命名参数
📋 常用符号类型
ISymbol - 所有符号的基接口
csharp
ISymbol symbol;
// 基本属性
string name = symbol.Name; // 符号名称
SymbolKind kind = symbol.Kind; // 符号类型
Accessibility accessibility = symbol.DeclaredAccessibility; // 访问修饰符
// 修饰符检查
bool isStatic = symbol.IsStatic;
bool isVirtual = symbol.IsVirtual;
bool isAbstract = symbol.IsAbstract;
// 包含关系
INamedTypeSymbol containingType = symbol.ContainingType;
INamespaceSymbol containingNamespace = symbol.ContainingNamespace;
// 特性
ImmutableArray<AttributeData> attributes = symbol.GetAttributes();
// 格式化输出
string displayString = symbol.ToDisplayString();INamedTypeSymbol - 类、结构、接口等
csharp
INamedTypeSymbol typeSymbol;
// 类型信息
TypeKind typeKind = typeSymbol.TypeKind; // Class, Struct, Interface, Enum
bool isReferenceType = typeSymbol.IsReferenceType;
bool isValueType = typeSymbol.IsValueType;
// 继承和接口
INamedTypeSymbol baseType = typeSymbol.BaseType;
ImmutableArray<INamedTypeSymbol> interfaces = typeSymbol.Interfaces;
// 成员访问
ImmutableArray<ISymbol> members = typeSymbol.GetMembers();
ImmutableArray<ISymbol> namedMembers = typeSymbol.GetMembers("MethodName");IMethodSymbol - 方法
csharp
IMethodSymbol methodSymbol;
// 方法信息
ITypeSymbol returnType = methodSymbol.ReturnType;
bool returnsVoid = methodSymbol.ReturnsVoid;
ImmutableArray<IParameterSymbol> parameters = methodSymbol.Parameters;
// 方法特性
bool isAsync = methodSymbol.IsAsync;
bool isExtensionMethod = methodSymbol.IsExtensionMethod;
MethodKind methodKind = methodSymbol.MethodKind;IPropertySymbol - 属性
csharp
IPropertySymbol propertySymbol;
// 属性信息
ITypeSymbol type = propertySymbol.Type;
bool isReadOnly = propertySymbol.IsReadOnly;
// 访问器
IMethodSymbol getMethod = propertySymbol.GetMethod;
IMethodSymbol setMethod = propertySymbol.SetMethod;⚠️ 常见陷阱
陷阱 1:符号比较
❌ 错误做法:
csharp
// 不要使用 == 或 Equals 比较符号
if (symbol1 == symbol2) { }
if (symbol1.Equals(symbol2)) { }✅ 正确做法:
csharp
// 使用 SymbolEqualityComparer
if (SymbolEqualityComparer.Default.Equals(symbol1, symbol2)) { }
// 在集合中使用
var symbolSet = new HashSet<ISymbol>(SymbolEqualityComparer.Default);陷阱 2:空引用检查
❌ 错误做法:
csharp
var classSymbol = semanticModel.GetDeclaredSymbol(classDecl);
string name = classSymbol.Name; // 可能抛出 NullReferenceException✅ 正确做法:
csharp
var classSymbol = semanticModel.GetDeclaredSymbol(classDecl);
if (classSymbol == null)
return;
string name = classSymbol.Name;陷阱 3:特性名称匹配
❌ 错误做法:
csharp
// 只检查一种形式
bool hasAttr = attributes.Any(a => a.AttributeClass?.Name == "MyAttribute");✅ 正确做法:
csharp
// 同时检查带和不带 "Attribute" 后缀
bool hasAttr = attributes.Any(a =>
a.AttributeClass?.Name == "MyAttribute" ||
a.AttributeClass?.Name == "MyAttributeAttribute");🎓 快速参考
获取语义信息的方法
| 方法 | 用途 | 返回类型 |
|---|---|---|
GetDeclaredSymbol(node) | 获取声明的符号 | ISymbol |
GetTypeInfo(expression) | 获取表达式类型 | TypeInfo |
GetSymbolInfo(expression) | 获取引用的符号 | SymbolInfo |
GetConstantValue(expression) | 获取常量值 | Optional<object> |
符号类型层次
ISymbol (基接口)
├── INamespaceSymbol (命名空间)
├── ITypeSymbol (类型)
│ ├── INamedTypeSymbol (类、结构、接口、枚举)
│ └── IArrayTypeSymbol (数组类型)
├── IMethodSymbol (方法)
├── IPropertySymbol (属性)
├── IFieldSymbol (字段)
└── IParameterSymbol (参数)⏭️ 下一步
完成基础学习后,你可以:
🔗 相关资源
最后更新: 2025-02-05