语义模型
📚 文档导航
本文档是语义模型系列的主文档,介绍基础概念和快速入门。
📖 文档系列
| 文档 | 内容 | 难度 |
|---|---|---|
| 语义模型基础 ⭐ | 基础概念、快速入门、核心功能 | 🟢 入门 |
| 类型信息获取 | GetTypeInfo、常量值、TypeInfo 详解 | 🟡 中级 |
| 符号信息获取 | GetDeclaredSymbol、GetSymbolInfo、符号遍历 | 🟡 中级 |
| 高级主题 | 泛型、继承、特性、类型转换 | 🔴 高级 |
| 最佳实践 | 性能优化、常见问题、实战场景 | 🟡 中级 |
什么是语义模型?
语义模型(Semantic Model)提供代码的语义信息,包括类型、符号、绑定等。
💡 核心概念
语义模型是连接语法树和类型系统的桥梁,它让你能够:
- 获取表达式的类型信息
- 查找符号的定义和引用
- 检查类型兼容性和转换
- 分析继承关系和接口实现
语法树 vs 语义模型
| 特性 | 语法树 | 语义模型 |
|---|---|---|
| 内容 | 代码结构 | 类型信息 |
| 信息 | 语法正确性 | 语义正确性 |
| 示例 | Person 是标识符 | Person 是类类型 |
| 性能 | 快速 | 较慢(需要解析) |
| 依赖 | 无 | 需要编译单元 |
⚠️ 重要区别
- 语法树:只知道代码的结构,不知道
Person是什么 - 语义模型:知道
Person是一个类,它的成员,继承关系等
快速开始
获取语义模型
csharp
// 从生成器上下文获取
var compilation = context.Compilation;
var syntaxTree = compilation.SyntaxTrees.First();
var semanticModel = compilation.GetSemanticModel(syntaxTree);💡 提示
每个语法树都有对应的语义模型,建议缓存语义模型以提高性能。
完整示例
csharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
public class SemanticModelExample
{
public void Demonstrate()
{
// 1. 准备源代码
var code = @"
using System;
namespace MyApp
{
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public void SayHello()
{
Console.WriteLine($""Hello, I'm {Name}"");
}
}
}
";
// 2. 创建语法树
var syntaxTree = CSharpSyntaxTree.ParseText(code);
// 3. 创建编译单元(需要引用)
var compilation = CSharpCompilation.Create("MyAssembly")
.AddReferences(
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Console).Assembly.Location)
)
.AddSyntaxTrees(syntaxTree);
// 4. 获取语义模型
var semanticModel = compilation.GetSemanticModel(syntaxTree);
// 5. 使用语义模型
var root = syntaxTree.GetRoot();
var classDecl = root.DescendantNodes()
.OfType<ClassDeclarationSyntax>()
.First();
var classSymbol = semanticModel.GetDeclaredSymbol(classDecl);
Console.WriteLine($"类名: {classSymbol.Name}");
Console.WriteLine($"命名空间: {classSymbol.ContainingNamespace}");
}
}核心功能
1. 获取符号信息
从语法节点获取对应的符号(Symbol)。
csharp
// 从语法节点获取符号
var classDeclaration = root.DescendantNodes()
.OfType<ClassDeclarationSyntax>()
.First();
var symbol = semanticModel.GetDeclaredSymbol(classDeclaration);
Console.WriteLine($"完整名称: {symbol.ToDisplayString()}");📚 深入学习
详细了解符号信息获取,请查看 符号信息获取详解
2. 获取类型信息
获取表达式的类型信息。
csharp
// 获取表达式的类型
var expression = node.DescendantNodes()
.OfType<ExpressionSyntax>()
.First();
var typeInfo = semanticModel.GetTypeInfo(expression);
Console.WriteLine($"类型: {typeInfo.Type.Name}");📚 深入学习
详细了解类型信息获取,请查看 类型信息获取详解
3. 查找符号
在编译单元中查找特定的符号。
csharp
// 查找特定类型
var personType = compilation.GetTypeByMetadataName("MyNamespace.Person");
// 查找基类
var baseType = personType.BaseType;
// 查找接口
var interfaces = personType.Interfaces;符号类型层次结构
语义模型中的符号遵循以下层次结构:
常用符号类型
INamedTypeSymbol(类、结构、接口)
csharp
var typeSymbol = semanticModel.GetDeclaredSymbol(classDeclaration)
as INamedTypeSymbol;
// 获取成员
var properties = typeSymbol.GetMembers()
.OfType<IPropertySymbol>();
// 检查类型
bool isClass = typeSymbol.TypeKind == TypeKind.Class;
bool isPublic = typeSymbol.DeclaredAccessibility == Accessibility.Public;IMethodSymbol(方法)
csharp
var methodSymbol = semanticModel.GetDeclaredSymbol(methodDeclaration)
as IMethodSymbol;
// 获取参数
var parameters = methodSymbol.Parameters;
// 获取返回类型
var returnType = methodSymbol.ReturnType;
// 检查是否异步
bool isAsync = methodSymbol.IsAsync;IPropertySymbol(属性)
csharp
var propertySymbol = semanticModel.GetDeclaredSymbol(propertyDeclaration)
as IPropertySymbol;
// 获取类型
var type = propertySymbol.Type;
// 检查访问器
bool hasGetter = propertySymbol.GetMethod != null;
bool hasSetter = propertySymbol.SetMethod != null;实际应用示例
检查类是否实现接口
csharp
bool ImplementsInterface(INamedTypeSymbol typeSymbol, string interfaceName)
{
return typeSymbol.AllInterfaces.Any(i =>
i.ToDisplayString() == interfaceName);
}获取所有公共属性
csharp
var publicProperties = typeSymbol.GetMembers()
.OfType<IPropertySymbol>()
.Where(p => p.DeclaredAccessibility == Accessibility.Public);检查类型兼容性
csharp
var conversion = semanticModel.ClassifyConversion(
sourceExpression,
targetType);
bool canConvert = conversion.Exists && !conversion.IsIdentity;在源生成器中使用
csharp
public void Execute(GeneratorExecutionContext context)
{
foreach (var syntaxTree in context.Compilation.SyntaxTrees)
{
var semanticModel = context.Compilation.GetSemanticModel(syntaxTree);
var root = syntaxTree.GetRoot();
var classes = root.DescendantNodes()
.OfType<ClassDeclarationSyntax>();
foreach (var cls in classes)
{
var symbol = semanticModel.GetDeclaredSymbol(cls)
as INamedTypeSymbol;
if (symbol == null) continue;
// 检查是否有特定特性
var hasAttribute = symbol.GetAttributes()
.Any(a => a.AttributeClass?.Name == "MyAttribute");
if (hasAttribute)
{
// 生成代码...
}
}
}
}语义模型的工作流程
性能考虑
✅ 推荐做法
csharp
// 缓存语义模型
var semanticModel = compilation.GetSemanticModel(syntaxTree);
// 批量处理
var symbols = nodes.Select(n => semanticModel.GetDeclaredSymbol(n));❌ 避免
csharp
// 不要重复获取语义模型
foreach (var node in nodes)
{
var model = compilation.GetSemanticModel(syntaxTree); // 慢!
var symbol = model.GetDeclaredSymbol(node);
}⚠️ 性能提示
语义模型的创建相对昂贵,应该缓存并重用。详细的性能优化技巧请查看 最佳实践。
下一步学习
📚 深入学习语义模型
🔗 相关主题
📖 API 参考
- 语义模型 API - 语义模型 API 详细参考
- 语义模型 API:类型和方法 - 类型和方法符号详解
- 类型系统深入 - 类型系统详细讲解
💡 实战示例
- 增量生成器示例 - 学习增量生成器的使用
- Builder 生成器 - 复杂的代码生成模式
🔗 外部资源
最后更新: 2025-01-21