语义模型 API
深入学习 Roslyn 语义模型 API 的使用方法
📚 本文档内容
本文档详细介绍语义模型 API 的使用,包括:
- 语义模型的获取和使用
- 符号类型和常用属性
- 特性处理
- 编译 API
- 代码生成 API
- 诊断 API
📋 文档信息
| 项目 | 信息 |
|---|---|
| 文档标题 | 语义模型 API |
| 所属系列 | 学习指南 |
| 难度级别 | ⭐⭐⭐⭐ 高级 |
| 预计阅读时间 | 60 分钟 |
| 前置知识 | 语法树 API |
| 相关文档 | Roslyn API 介绍、常见模式 |
🎯 学习目标
学完本文档后,你将能够:
- ✅ 理解语义模型的作用和使用场景
- ✅ 掌握符号信息的获取方法
- ✅ 熟练处理特性数据
- ✅ 理解编译 API 的使用
- ✅ 掌握代码生成技巧
- ✅ 能够创建和报告诊断信息
📑 快速导航
| 章节 | 内容概要 | 跳转链接 |
|---|---|---|
| 语义模型 API | 获取符号信息和类型信息 | 查看 |
| 符号类型 | 各种符号类型的使用 | 查看 |
| 特性处理 | 处理特性数据 | 查看 |
| 编译 API | 创建和操作编译 | 查看 |
| 代码生成 | 使用 SourceText 和 SyntaxFactory | 查看 |
| 诊断 API | 创建和报告诊断 | 查看 |
概览
语义模型提供代码的语义信息,包括类型信息、符号解析、类型检查等。它建立在语法树之上,提供更深层次的代码理解。
语义模型 API
获取符号信息
csharp
// 从语义模型获取符号
SemanticModel semanticModel = compilation.GetSemanticModel(syntaxTree);
// 获取声明的符号
ISymbol symbol = semanticModel.GetDeclaredSymbol(syntaxNode);
// 获取类型符号
INamedTypeSymbol classSymbol = semanticModel.GetDeclaredSymbol(classDeclaration);
// 获取方法符号
IMethodSymbol methodSymbol = semanticModel.GetDeclaredSymbol(methodDeclaration);
// 获取属性符号
IPropertySymbol propertySymbol = semanticModel.GetDeclaredSymbol(propertyDeclaration);
// 获取字段符号
IFieldSymbol fieldSymbol = semanticModel.GetDeclaredSymbol(variableDeclarator);
// 获取表达式的类型信息
TypeInfo typeInfo = semanticModel.GetTypeInfo(expression);
ITypeSymbol type = typeInfo.Type;
ITypeSymbol convertedType = typeInfo.ConvertedType;
// 获取符号信息(用于引用)
SymbolInfo symbolInfo = semanticModel.GetSymbolInfo(expression);
ISymbol referencedSymbol = symbolInfo.Symbol;符号类型和常用属性
INamedTypeSymbol - 命名类型(类、接口、结构等)
csharp
INamedTypeSymbol typeSymbol;
string typeName = typeSymbol.Name;
string fullName = typeSymbol.ToDisplayString();
TypeKind kind = typeSymbol.TypeKind; // Class, Interface, Struct, Enum, Delegate
INamespaceSymbol containingNamespace = typeSymbol.ContainingNamespace;
INamedTypeSymbol baseType = typeSymbol.BaseType;
ImmutableArray<INamedTypeSymbol> interfaces = typeSymbol.Interfaces;
ImmutableArray<ISymbol> members = typeSymbol.GetMembers();
ImmutableArray<IMethodSymbol> methods = typeSymbol.GetMembers().OfType<IMethodSymbol>();
ImmutableArray<IPropertySymbol> properties = typeSymbol.GetMembers().OfType<IPropertySymbol>();IMethodSymbol - 方法
csharp
IMethodSymbol methodSymbol;
string methodName = methodSymbol.Name;
ITypeSymbol returnType = methodSymbol.ReturnType;
ImmutableArray<IParameterSymbol> parameters = methodSymbol.Parameters;
bool isStatic = methodSymbol.IsStatic;
bool isAsync = methodSymbol.IsAsync;
bool isExtensionMethod = methodSymbol.IsExtensionMethod;
MethodKind methodKind = methodSymbol.MethodKind; // Ordinary, Constructor, PropertyGet, etc.IPropertySymbol - 属性
csharp
IPropertySymbol propertySymbol;
string propertyName = propertySymbol.Name;
ITypeSymbol propertyType = propertySymbol.Type;
IMethodSymbol getMethod = propertySymbol.GetMethod;
IMethodSymbol setMethod = propertySymbol.SetMethod;
bool isReadOnly = propertySymbol.IsReadOnly;
bool isWriteOnly = propertySymbol.IsWriteOnly;IFieldSymbol - 字段
csharp
IFieldSymbol fieldSymbol;
string fieldName = fieldSymbol.Name;
ITypeSymbol fieldType = fieldSymbol.Type;
bool isReadOnly = fieldSymbol.IsReadOnly;
bool isConst = fieldSymbol.IsConst;
object constantValue = fieldSymbol.ConstantValue;通用符号属性
csharp
ISymbol symbol;
string name = symbol.Name;
Accessibility accessibility = symbol.DeclaredAccessibility; // Public, Private, Protected, Internal
bool isStatic = symbol.IsStatic;
bool isVirtual = symbol.IsVirtual;
bool isAbstract = symbol.IsAbstract;
bool isSealed = symbol.IsSealed;
ImmutableArray<AttributeData> attributes = symbol.GetAttributes();特性 (Attribute) 处理
csharp
// 获取符号的所有特性
ImmutableArray<AttributeData> attributes = symbol.GetAttributes();
// 查找特定特性
AttributeData myAttribute = attributes.FirstOrDefault(a =>
a.AttributeClass?.Name == "MyAttribute");
// 检查是否有特定特性
bool hasAttribute = attributes.Any(a =>
a.AttributeClass?.ToDisplayString() == "Namespace.MyAttribute");
// 获取特性参数
if (myAttribute != null)
{
// 构造函数参数
ImmutableArray<TypedConstant> constructorArgs = myAttribute.ConstructorArguments;
object firstArg = constructorArgs[0].Value;
// 命名参数
ImmutableArray<KeyValuePair<string, TypedConstant>> namedArgs = myAttribute.NamedArguments;
foreach (var arg in namedArgs)
{
string paramName = arg.Key;
object paramValue = arg.Value.Value;
}
}编译 API
创建和操作编译
csharp
// 创建编译
CSharpCompilation compilation = CSharpCompilation.Create(
assemblyName: "MyAssembly",
syntaxTrees: new[] { syntaxTree },
references: new[] {
MetadataReference.CreateFromFile(typeof(object).Assembly.Location)
},
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
// 添加语法树
compilation = compilation.AddSyntaxTrees(newTree);
// 获取所有语法树
ImmutableArray<SyntaxTree> trees = compilation.SyntaxTrees;
// 获取程序集符号
IAssemblySymbol assemblySymbol = compilation.Assembly;
// 获取全局命名空间
INamespaceSymbol globalNamespace = compilation.GlobalNamespace;
// 查找类型
INamedTypeSymbol typeSymbol = compilation.GetTypeByMetadataName("Namespace.ClassName");
// 获取特殊类型
INamedTypeSymbol objectType = compilation.GetSpecialType(SpecialType.System_Object);
INamedTypeSymbol stringType = compilation.GetSpecialType(SpecialType.System_String);
INamedTypeSymbol intType = compilation.GetSpecialType(SpecialType.System_Int32);
// 类型转换检查
Conversion conversion = compilation.ClassifyConversion(sourceType, targetType);
bool isImplicit = conversion.IsImplicit;
bool isExplicit = conversion.IsExplicit;代码生成 API
使用 SourceText
csharp
using Microsoft.CodeAnalysis.Text;
// 从字符串创建
SourceText sourceText = SourceText.From(code, Encoding.UTF8);
// 添加到生成器上下文
context.AddSource("FileName.g.cs", sourceText);
// 或直接使用字符串
context.AddSource("FileName.g.cs", code);使用 SyntaxFactory 构建语法树
csharp
using Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
// 创建类声明
ClassDeclarationSyntax classDecl = ClassDeclaration("MyClass")
.AddModifiers(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.PartialKeyword))
.AddMembers(
PropertyDeclaration(
PredefinedType(Token(SyntaxKind.StringKeyword)),
"Name")
.AddModifiers(Token(SyntaxKind.PublicKeyword))
.AddAccessorListAccessors(
AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken)),
AccessorDeclaration(SyntaxKind.SetAccessorDeclaration)
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken))));
// 创建命名空间
NamespaceDeclarationSyntax namespaceDecl = NamespaceDeclaration(
IdentifierName("MyNamespace"))
.AddMembers(classDecl);
// 创建编译单元
CompilationUnitSyntax compilationUnit = CompilationUnit()
.AddUsings(UsingDirective(IdentifierName("System")))
.AddMembers(namespaceDecl)
.NormalizeWhitespace();
// 转换为字符串
string code = compilationUnit.ToFullString();诊断 API
创建和报告诊断
csharp
// 定义诊断描述符
DiagnosticDescriptor descriptor = new DiagnosticDescriptor(
id: "SG0001",
title: "Invalid usage",
messageFormat: "The class '{0}' must be partial",
category: "SourceGenerator",
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: "Classes using this attribute must be declared as partial.");
// 创建诊断
Diagnostic diagnostic = Diagnostic.Create(
descriptor,
location: node.GetLocation(),
messageArgs: className);
// 报告诊断
context.ReportDiagnostic(diagnostic);
// 诊断严重级别
DiagnosticSeverity.Error // 编译错误
DiagnosticSeverity.Warning // 警告
DiagnosticSeverity.Info // 信息
DiagnosticSeverity.Hidden // 隐藏(IDE 提示)增量生成器管道 API
管道操作
csharp
// Select - 转换数据
IncrementalValuesProvider<string> names = provider
.Select(static (data, _) => data.Name);
// Where - 过滤数据
IncrementalValuesProvider<ClassInfo> filtered = provider
.Where(static data => data.IsValid);
// Combine - 组合两个提供者
IncrementalValueProvider<(ClassInfo, ConfigInfo)> combined =
classProvider.Combine(configProvider);
// Collect - 收集所有值到集合
IncrementalValueProvider<ImmutableArray<ClassInfo>> collected =
provider.Collect();
// SelectMany - 展平集合
IncrementalValuesProvider<PropertyInfo> properties = provider
.SelectMany(static (classInfo, _) => classInfo.Properties);ForAttributeWithMetadataName (.NET 7+)
csharp
// 高性能的基于特性的查找
var provider = context.SyntaxProvider
.ForAttributeWithMetadataName(
fullyQualifiedMetadataName: "MyNamespace.MyAttribute",
predicate: static (node, _) => node is ClassDeclarationSyntax,
transform: static (context, _) => {
var classSymbol = (INamedTypeSymbol)context.TargetSymbol;
var attributeData = context.Attributes[0];
return new ClassInfo(classSymbol, attributeData);
});💡 关键要点
语义模型提供深层信息
- 类型信息、符号解析、类型检查
- 比语法分析慢,但提供更多信息
符号是语义的核心
ISymbol表示命名实体- 提供类型、可访问性、修饰符等信息
特性处理
- 使用
GetAttributes()获取特性 - 区分构造函数参数和命名参数
- 使用
编译 API
- 管理语法树和引用
- 提供类型查找和转换检查
代码生成
- 使用
SourceText或SyntaxFactory SyntaxFactory更类型安全但更复杂
- 使用
诊断报告
- 使用
DiagnosticDescriptor定义诊断 - 选择合适的严重级别
- 使用
🔗 相关文档
- Roslyn API 介绍 - API 基础概念
- 语法树 API - 语法树操作详解
- 常见模式 - 实用代码模式
- 最佳实践 - 性能优化建议
- 返回学习指南
📚 下一步
学习完语义模型 API 后,建议继续学习: