语义模型最佳实践
📚 文档导航
本文档介绍语义模型的最佳实践、性能优化技巧和常见问题解答。
📖 文档系列
| 文档 | 内容 | 难度 |
|---|---|---|
| 语义模型基础 | 基础概念、快速入门、核心功能 | 🟢 入门 |
| 类型信息获取 | GetTypeInfo、常量值、TypeInfo 详解 | 🟡 中级 |
| 符号信息获取 | GetDeclaredSymbol、GetSymbolInfo、符号遍历 | 🟡 中级 |
| 高级主题 | 泛型、继承、特性、类型转换 | 🔴 高级 |
| 最佳实践 ⭐ | 性能优化、常见问题、实战场景 | 🟡 中级 |
性能优化
缓存语义模型
❌ 错误做法
csharp
// 不要重复获取语义模型
public void ProcessNodes(Compilation compilation, SyntaxTree tree, IEnumerable<SyntaxNode> nodes)
{
foreach (var node in nodes)
{
// 每次循环都创建新的语义模型 - 非常慢!
var model = compilation.GetSemanticModel(tree);
var symbol = model.GetDeclaredSymbol(node);
// 处理...
}
}✅ 正确做法
csharp
// 缓存语义模型
public void ProcessNodes(Compilation compilation, SyntaxTree tree, IEnumerable<SyntaxNode> nodes)
{
// 只创建一次语义模型
var model = compilation.GetSemanticModel(tree);
foreach (var node in nodes)
{
var symbol = model.GetDeclaredSymbol(node);
// 处理...
}
}使用缓存字典
csharp
public class SemanticModelCache
{
private readonly Dictionary<SyntaxTree, SemanticModel> _cache = new();
private readonly Compilation _compilation;
public SemanticModelCache(Compilation compilation)
{
_compilation = compilation;
}
public SemanticModel GetSemanticModel(SyntaxTree tree)
{
if (!_cache.TryGetValue(tree, out var model))
{
model = _compilation.GetSemanticModel(tree);
_cache[tree] = model;
}
return model;
}
}批量处理
❌ 错误做法
csharp
// 逐个处理节点
public void ProcessClasses(Compilation compilation)
{
foreach (var tree in compilation.SyntaxTrees)
{
var model = compilation.GetSemanticModel(tree);
var root = tree.GetRoot();
foreach (var classDecl in root.DescendantNodes().OfType<ClassDeclarationSyntax>())
{
var symbol = model.GetDeclaredSymbol(classDecl);
ProcessClass(symbol);
}
}
}✅ 正确做法
csharp
// 批量收集后处理
public void ProcessClasses(Compilation compilation)
{
// 1. 批量收集所有类符号
var classSymbols = new List<INamedTypeSymbol>();
foreach (var tree in compilation.SyntaxTrees)
{
var model = compilation.GetSemanticModel(tree);
var root = tree.GetRoot();
var symbols = root.DescendantNodes()
.OfType<ClassDeclarationSyntax>()
.Select(c => model.GetDeclaredSymbol(c))
.Where(s => s != null);
classSymbols.AddRange(symbols);
}
// 2. 批量处理
foreach (var symbol in classSymbols)
{
ProcessClass(symbol);
}
}性能优化流程图
最佳实践 vs 反模式
| 场景 | ✅ 最佳实践 | ❌ 反模式 |
|---|---|---|
| 获取语义模型 | 缓存每个语法树的语义模型 | 每次都重新创建 |
| 符号比较 | 使用 SymbolEqualityComparer.Default | 使用 == 或 Equals() |
| 类型检查 | 使用 is 和模式匹配 | 使用字符串比较类型名 |
| 特性查询 | 使用 GetAttributes() | 解析语法节点的特性列表 |
| 泛型处理 | 使用 OriginalDefinition 比较 | 比较完整类型字符串 |
| 成员查找 | 使用 GetMembers(name) | 遍历所有成员后过滤 |
| 类型转换 | 使用 ClassifyConversion | 手动检查类型关系 |
真实应用场景
场景 1: 验证类是否满足生成器要求
csharp
public class GeneratorValidator
{
public bool ValidateClass(INamedTypeSymbol classSymbol)
{
// 1. 必须是 partial 类
if (!IsPartialClass(classSymbol))
{
return false;
}
// 2. 必须有特定特性
if (!classSymbol.GetAttributes()
.Any(a => a.AttributeClass?.Name == "GenerateBuilderAttribute"))
{
return false;
}
// 3. 必须有公共属性
var publicProperties = classSymbol.GetMembers()
.OfType<IPropertySymbol>()
.Where(p => p.DeclaredAccessibility == Accessibility.Public);
if (!publicProperties.Any())
{
return false;
}
return true;
}
private bool IsPartialClass(INamedTypeSymbol classSymbol)
{
return classSymbol.DeclaringSyntaxReferences
.Select(r => r.GetSyntax())
.OfType<ClassDeclarationSyntax>()
.Any(c => c.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword)));
}
}场景 2: 生成依赖注入代码
csharp
public class DICodeGenerator
{
public string GenerateDIRegistration(INamedTypeSymbol classSymbol)
{
var className = classSymbol.Name;
var interfaceName = $"I{className}";
// 检查是否实现了接口
var implementsInterface = classSymbol.AllInterfaces
.Any(i => i.Name == interfaceName);
if (implementsInterface)
{
return $"services.AddScoped<{interfaceName}, {className}>();";
}
else
{
return $"services.AddScoped<{className}>();";
}
}
}场景 3: 查找所有带特定特性的类
csharp
public class AttributeScanner
{
public IEnumerable<INamedTypeSymbol> FindClassesWithAttribute(
Compilation compilation,
string attributeName)
{
var results = new List<INamedTypeSymbol>();
foreach (var tree in compilation.SyntaxTrees)
{
var model = compilation.GetSemanticModel(tree);
var root = tree.GetRoot();
var classes = root.DescendantNodes()
.OfType<ClassDeclarationSyntax>();
foreach (var classDecl in classes)
{
var symbol = model.GetDeclaredSymbol(classDecl) as INamedTypeSymbol;
if (symbol != null && HasAttribute(symbol, attributeName))
{
results.Add(symbol);
}
}
}
return results;
}
private bool HasAttribute(INamedTypeSymbol symbol, string attributeName)
{
return symbol.GetAttributes()
.Any(a => a.AttributeClass?.Name == attributeName ||
a.AttributeClass?.ToDisplayString() == attributeName);
}
}场景 4: 生成 ToString 方法
csharp
public class ToStringGenerator
{
public string GenerateToString(INamedTypeSymbol classSymbol)
{
var properties = classSymbol.GetMembers()
.OfType<IPropertySymbol>()
.Where(p => p.DeclaredAccessibility == Accessibility.Public)
.Where(p => p.GetMethod != null);
var propertyStrings = properties
.Select(p => $"{p.Name} = {{{p.Name}}}");
var format = string.Join(", ", propertyStrings);
return $@"
public override string ToString()
{{
return $""{classSymbol.Name} {{ {format} }}"";
}}";
}
}常见问题解答
Q1: 语义模型和语法树的区别?
A: 语法树只有结构信息,语义模型有类型和符号信息。
csharp
// 语法树:知道这是一个标识符
var identifier = node as IdentifierNameSyntax;
Console.WriteLine(identifier.Identifier.Text); // "Person"
// 语义模型:知道这是一个类类型
var symbolInfo = model.GetSymbolInfo(identifier);
var typeSymbol = symbolInfo.Symbol as INamedTypeSymbol;
Console.WriteLine(typeSymbol.TypeKind); // TypeKind.ClassQ2: 如何检查两个类型是否相同?
A: 使用 SymbolEqualityComparer.Default.Equals(type1, type2)
csharp
// ✅ 正确
bool areEqual = SymbolEqualityComparer.Default.Equals(type1, type2);
// ❌ 错误
bool areEqual = type1 == type2; // 不要这样做!
bool areEqual = type1.Equals(type2); // 不要这样做!Q3: 如何获取方法的所有重载?
A: 使用 containingType.GetMembers(methodName).OfType<IMethodSymbol>()
csharp
public IEnumerable<IMethodSymbol> GetMethodOverloads(
INamedTypeSymbol containingType,
string methodName)
{
return containingType.GetMembers(methodName)
.OfType<IMethodSymbol>();
}Q4: 如何判断一个类型是否实现了特定接口?
A: 使用 AllInterfaces 属性检查所有实现的接口
csharp
bool ImplementsInterface(INamedTypeSymbol type, string interfaceName)
{
return type.AllInterfaces.Any(i =>
i.ToDisplayString() == interfaceName);
}
// 示例:检查是否实现 IEnumerable<T>
var implementsIEnumerable = ImplementsInterface(
typeSymbol,
"System.Collections.Generic.IEnumerable<T>");Q5: 如何获取泛型类型的类型参数?
A: 使用 TypeArguments 属性获取构造的泛型类型的类型参数
csharp
// 对于 List<string>
if (typeSymbol.IsGenericType)
{
var typeArgs = typeSymbol.TypeArguments;
// typeArgs[0] 是 string 类型
var elementType = typeArgs[0];
}Q6: 如何检查方法是否是异步的?
A: 使用 IMethodSymbol.IsAsync 属性
csharp
public bool IsAsyncMethod(IMethodSymbol method)
{
return method.IsAsync;
}Q7: 如何获取属性的 getter 和 setter?
A: 使用 IPropertySymbol.GetMethod 和 IPropertySymbol.SetMethod
csharp
public void AnalyzeProperty(IPropertySymbol property)
{
bool hasGetter = property.GetMethod != null;
bool hasSetter = property.SetMethod != null;
bool isReadOnly = hasGetter && !hasSetter;
bool isWriteOnly = !hasGetter && hasSetter;
bool isReadWrite = hasGetter && hasSetter;
}Q8: 如何检查类型是否可以为 null?
A: 检查类型是引用类型还是可空值类型
csharp
public bool CanBeNull(ITypeSymbol type)
{
// 引用类型可以为 null
if (type.IsReferenceType) return true;
// 可空值类型可以为 null
if (type is INamedTypeSymbol namedType &&
namedType.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T)
{
return true;
}
return false;
}Q9: 如何获取符号的定义位置?
A: 使用 ISymbol.Locations 属性
csharp
public Location GetDefinitionLocation(ISymbol symbol)
{
return symbol.Locations.FirstOrDefault();
}
public string GetDefinitionFilePath(ISymbol symbol)
{
var location = symbol.Locations.FirstOrDefault();
return location?.SourceTree?.FilePath;
}Q10: 如何检查类是否是 partial 类?
A: 检查语法声明中是否有 partial 修饰符
csharp
public bool IsPartialClass(INamedTypeSymbol classSymbol)
{
return classSymbol.DeclaringSyntaxReferences
.Select(r => r.GetSyntax())
.OfType<ClassDeclarationSyntax>()
.Any(c => c.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword)));
}调试技巧
1. 打印符号信息
csharp
public void DebugSymbol(ISymbol symbol)
{
Console.WriteLine($"符号: {symbol.ToDisplayString()}");
Console.WriteLine($" 种类: {symbol.Kind}");
Console.WriteLine($" 名称: {symbol.Name}");
Console.WriteLine($" 命名空间: {symbol.ContainingNamespace?.ToDisplayString()}");
Console.WriteLine($" 程序集: {symbol.ContainingAssembly?.Name}");
Console.WriteLine($" 访问级别: {symbol.DeclaredAccessibility}");
Console.WriteLine($" 是静态: {symbol.IsStatic}");
Console.WriteLine($" 是抽象: {symbol.IsAbstract}");
Console.WriteLine($" 是虚方法: {symbol.IsVirtual}");
Console.WriteLine($" 是密封: {symbol.IsSealed}");
}2. 打印类型信息
csharp
public void DebugType(ITypeSymbol type)
{
Console.WriteLine($"类型: {type.ToDisplayString()}");
Console.WriteLine($" 种类: {type.TypeKind}");
Console.WriteLine($" 是值类型: {type.IsValueType}");
Console.WriteLine($" 是引用类型: {type.IsReferenceType}");
Console.WriteLine($" 是泛型: {type is INamedTypeSymbol nt && nt.IsGenericType}");
Console.WriteLine($" 是数组: {type.TypeKind == TypeKind.Array}");
if (type is INamedTypeSymbol namedType)
{
Console.WriteLine($" 基类: {namedType.BaseType?.ToDisplayString()}");
Console.WriteLine($" 接口:");
foreach (var iface in namedType.AllInterfaces)
{
Console.WriteLine($" - {iface.ToDisplayString()}");
}
}
}3. 打印特性信息
csharp
public void DebugAttributes(ISymbol symbol)
{
Console.WriteLine($"符号: {symbol.Name}");
Console.WriteLine($"特性:");
foreach (var attr in symbol.GetAttributes())
{
Console.WriteLine($" [{attr.AttributeClass?.Name}]");
if (attr.ConstructorArguments.Any())
{
Console.WriteLine($" 构造函数参数:");
foreach (var arg in attr.ConstructorArguments)
{
Console.WriteLine($" - {arg.Type?.ToDisplayString()}: {arg.Value}");
}
}
if (attr.NamedArguments.Any())
{
Console.WriteLine($" 命名参数:");
foreach (var arg in attr.NamedArguments)
{
Console.WriteLine($" - {arg.Key} = {arg.Value.Value}");
}
}
}
}实用工具类库
完整的符号工具类
csharp
public static class SymbolExtensions
{
// 检查是否是公共的
public static bool IsPublic(this ISymbol symbol)
{
return symbol.DeclaredAccessibility == Accessibility.Public;
}
// 检查是否是私有的
public static bool IsPrivate(this ISymbol symbol)
{
return symbol.DeclaredAccessibility == Accessibility.Private;
}
// 检查是否是受保护的
public static bool IsProtected(this ISymbol symbol)
{
return symbol.DeclaredAccessibility == Accessibility.Protected;
}
// 检查是否是内部的
public static bool IsInternal(this ISymbol symbol)
{
return symbol.DeclaredAccessibility == Accessibility.Internal;
}
// 获取完整名称
public static string GetFullName(this ISymbol symbol)
{
return symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
}
// 检查是否有特定特性
public static bool HasAttribute(this ISymbol symbol, string attributeName)
{
return symbol.GetAttributes()
.Any(a => a.AttributeClass?.Name == attributeName ||
a.AttributeClass?.ToDisplayString() == attributeName);
}
// 获取特定特性
public static AttributeData GetAttribute(this ISymbol symbol, string attributeName)
{
return symbol.GetAttributes()
.FirstOrDefault(a => a.AttributeClass?.Name == attributeName ||
a.AttributeClass?.ToDisplayString() == attributeName);
}
}下一步
🔗 相关资源
深入学习
API 参考
- 语义模型 API - 语义模型 API 详细参考
- 语义模型 API:类型和方法 - 类型和方法符号详解
- 类型系统深入 - 类型系统详细讲解
实战示例
- 增量生成器示例 - 学习增量生成器的使用
- Builder 生成器 - 复杂的代码生成模式
官方文档
最后更新: 2025-01-21