最佳实践和常见问题
语义模型 API 的最佳实践、反模式和常见问题解答
📋 文档信息
| 属性 | 值 |
|---|---|
| 难度 | 中级 |
| 阅读时间 | 25 分钟 |
| 前置知识 | 语义模型基础知识 |
| 相关文档 | ISymbol 基础、符号比较 |
🎯 学习目标
完成本文档后,你将能够:
- ✅ 掌握语义模型 API 的最佳实践
- ✅ 避免常见的反模式和错误
- ✅ 优化性能和内存使用
- ✅ 解决常见问题
- ✅ 编写高质量的源生成器代码
📚 快速导航
| 主题 | 说明 |
|---|---|
| 性能优化 | 提高性能的最佳实践 |
| 符号比较 | 正确比较符号 |
| 空值处理 | 安全的空值检查 |
| 类型检查 | 常用的类型检查模式 |
| 反模式 | 应该避免的做法 |
| 常见问题 | FAQ |
性能优化
缓存 SemanticModel
csharp
/// <summary>
/// 缓存 SemanticModel 以提高性能
/// </summary>
public class SemanticModelCache
{
private readonly ConcurrentDictionary<SyntaxTree, SemanticModel> _cache = new();
// ✅ 推荐:缓存 SemanticModel
public SemanticModel GetOrCreateSemanticModel(
Compilation compilation,
SyntaxTree tree)
{
return _cache.GetOrAdd(tree, t => compilation.GetSemanticModel(t));
}
// ❌ 不推荐:每次都创建新的 SemanticModel
public SemanticModel CreateSemanticModel(
Compilation compilation,
SyntaxTree tree)
{
return compilation.GetSemanticModel(tree); // 性能差
}
}避免重复调用
csharp
/// <summary>
/// 避免重复调用 GetDeclaredSymbol
/// </summary>
public class AvoidRepeatCalls
{
// ✅ 推荐:只调用一次
public void GoodPattern(ClassDeclarationSyntax classDecl, SemanticModel model)
{
var classSymbol = model.GetDeclaredSymbol(classDecl);
if (classSymbol == null) return;
// 使用 classSymbol 进行后续操作
var name = classSymbol.Name;
var members = classSymbol.GetMembers();
var baseType = classSymbol.BaseType;
}
// ❌ 不推荐:多次调用
public void BadPattern(ClassDeclarationSyntax classDecl, SemanticModel model)
{
var name = model.GetDeclaredSymbol(classDecl)?.Name;
var members = model.GetDeclaredSymbol(classDecl)?.GetMembers();
var baseType = model.GetDeclaredSymbol(classDecl)?.BaseType;
}
}批量处理符号
csharp
/// <summary>
/// 批量处理符号以提高性能
/// </summary>
public class BatchProcessing
{
// ✅ 推荐:使用 HashSet 去重
public void ProcessUniqueSymbols(IEnumerable<ISymbol> symbols)
{
var uniqueSymbols = new HashSet<ISymbol>(
symbols,
SymbolEqualityComparer.Default);
foreach (var symbol in uniqueSymbols)
{
// 处理每个唯一符号
}
}
// ✅ 推荐:使用 LINQ 批量操作
public List<IMethodSymbol> GetPublicMethods(INamedTypeSymbol type)
{
return type.GetMembers()
.OfType<IMethodSymbol>()
.Where(m => m.DeclaredAccessibility == Accessibility.Public)
.ToList();
}
}符号比较最佳实践
使用 SymbolEqualityComparer
csharp
/// <summary>
/// 正确使用 SymbolEqualityComparer
/// </summary>
public class SymbolComparison
{
// ✅ 推荐:使用 SymbolEqualityComparer
public bool CompareSymbols(ISymbol symbol1, ISymbol symbol2)
{
return SymbolEqualityComparer.Default.Equals(symbol1, symbol2);
}
// ❌ 不推荐:使用 ==
public bool WrongComparison(ISymbol symbol1, ISymbol symbol2)
{
return symbol1 == symbol2; // 不可靠!
}
// ✅ 推荐:在集合中使用
public void UseInCollections()
{
var symbolSet = new HashSet<ISymbol>(SymbolEqualityComparer.Default);
var symbolDict = new Dictionary<ISymbol, string>(SymbolEqualityComparer.Default);
}
// ✅ 推荐:在 LINQ 中使用
public void UseInLinq(IEnumerable<ISymbol> symbols)
{
var uniqueSymbols = symbols.Distinct(SymbolEqualityComparer.Default);
var grouped = symbols.GroupBy(s => s, SymbolEqualityComparer.Default);
}
}空值处理
安全的符号访问
csharp
/// <summary>
/// 安全的符号访问模式
/// </summary>
public class SafeSymbolAccess
{
// ✅ 推荐:使用 null 条件运算符
public void SafeAccess(SyntaxNode node, SemanticModel model)
{
var symbol = model.GetDeclaredSymbol(node);
var name = symbol?.Name;
var type = symbol?.ContainingType?.Name;
}
// ✅ 推荐:使用模式匹配
public void PatternMatching(SyntaxNode node, SemanticModel model)
{
if (model.GetDeclaredSymbol(node) is INamedTypeSymbol typeSymbol)
{
// 安全使用 typeSymbol
var members = typeSymbol.GetMembers();
}
}
// ✅ 推荐:检查集合是否为空
public void SafeCollectionAccess(ISymbol symbol)
{
var attributes = symbol?.GetAttributes() ??
ImmutableArray<AttributeData>.Empty;
if (attributes.Any())
{
// 处理特性
}
}
}安全的类型信息访问
csharp
/// <summary>
/// 安全的类型信息访问
/// </summary>
public class SafeTypeInfoAccess
{
// ✅ 推荐:检查 Type 是否为 null
public void SafeTypeAccess(ExpressionSyntax expression, SemanticModel model)
{
var typeInfo = model.GetTypeInfo(expression);
if (typeInfo.Type != null)
{
var typeName = typeInfo.Type.ToDisplayString();
}
}
// ✅ 推荐:检查 ConvertedType
public void SafeConvertedTypeAccess(ExpressionSyntax expression, SemanticModel model)
{
var typeInfo = model.GetTypeInfo(expression);
if (typeInfo.ConvertedType != null &&
!SymbolEqualityComparer.Default.Equals(
typeInfo.Type,
typeInfo.ConvertedType))
{
// 类型发生了转换
}
}
}类型检查模式
常用的类型检查
csharp
/// <summary>
/// 常用的类型检查模式
/// </summary>
public class TypeCheckPatterns
{
// ✅ 推荐:检查特定的内置类型
public bool IsBuiltInType(ITypeSymbol type, SpecialType specialType)
{
return type.SpecialType == specialType;
}
// ✅ 推荐:检查字符串类型
public bool IsString(ITypeSymbol type)
{
return type.SpecialType == SpecialType.System_String;
}
// ✅ 推荐:检查可空值类型
public bool IsNullableValueType(ITypeSymbol type)
{
return type is INamedTypeSymbol namedType &&
namedType.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T;
}
// ✅ 推荐:检查集合类型
public bool IsCollection(ITypeSymbol type, Compilation compilation)
{
var ienumerableType = compilation.GetSpecialType(
SpecialType.System_Collections_IEnumerable);
return type.AllInterfaces.Any(i =>
SymbolEqualityComparer.Default.Equals(i, ienumerableType));
}
// ✅ 推荐:检查泛型集合
public bool IsGenericCollection(ITypeSymbol type, Compilation compilation)
{
var genericIEnumerable = compilation.GetTypeByMetadataName(
"System.Collections.Generic.IEnumerable`1");
if (genericIEnumerable == null)
return false;
return type.AllInterfaces.Any(i =>
SymbolEqualityComparer.Default.Equals(
i.OriginalDefinition,
genericIEnumerable));
}
// ✅ 推荐:获取集合的元素类型
public ITypeSymbol GetCollectionElementType(
ITypeSymbol type,
Compilation compilation)
{
var genericIEnumerable = compilation.GetTypeByMetadataName(
"System.Collections.Generic.IEnumerable`1");
if (genericIEnumerable == null)
return null;
var ienumerableInterface = type.AllInterfaces.FirstOrDefault(i =>
SymbolEqualityComparer.Default.Equals(
i.OriginalDefinition,
genericIEnumerable));
return ienumerableInterface?.TypeArguments.FirstOrDefault();
}
}反模式
应该避免的做法
csharp
/// <summary>
/// 应该避免的反模式
/// </summary>
public class AntiPatterns
{
// ❌ 反模式 1: 使用 == 比较符号
public bool WrongSymbolComparison(ISymbol symbol1, ISymbol symbol2)
{
return symbol1 == symbol2; // 不可靠!
}
// ❌ 反模式 2: 重复调用 GetDeclaredSymbol
public void WrongRepeatCalls(ClassDeclarationSyntax classDecl, SemanticModel model)
{
var name = model.GetDeclaredSymbol(classDecl)?.Name;
var members = model.GetDeclaredSymbol(classDecl)?.GetMembers();
var baseType = model.GetDeclaredSymbol(classDecl)?.BaseType;
}
// ❌ 反模式 3: 忽略空值检查
public string WrongNullHandling(ExpressionSyntax expr, SemanticModel model)
{
return model.GetTypeInfo(expr).Type.ToDisplayString(); // 可能 NullReferenceException
}
// ❌ 反模式 4: 使用字符串比较类型
public bool WrongTypeComparison(ITypeSymbol type)
{
return type.ToDisplayString() == "string"; // 不可靠!
}
// ❌ 反模式 5: 忘记处理泛型
public string WrongGenericHandling(ITypeSymbol type)
{
return type.Name; // 泛型类型会显示为 List<T>
}
// ❌ 反模式 6: 在循环中创建 SemanticModel
public void WrongSemanticModelCreation(Compilation compilation, List<SyntaxTree> trees)
{
foreach (var tree in trees)
{
var model = compilation.GetSemanticModel(tree); // 性能差
// 处理...
}
}
// ❌ 反模式 7: 忽略 SymbolKind 检查
public void WrongSymbolCast(ISymbol member)
{
var method = (IMethodSymbol)member; // 可能失败
}
// ❌ 反模式 8: 不使用比较器的集合
public void WrongCollectionUsage()
{
var set = new HashSet<ISymbol>(); // 没有指定比较器
}
}常见问题解答
Q1: 如何获取 SemanticModel?
A: 有两种主要方式:
csharp
// 方式 1: 从 Compilation 获取
Compilation compilation = /* ... */;
SyntaxTree syntaxTree = /* ... */;
SemanticModel semanticModel = compilation.GetSemanticModel(syntaxTree);
// 方式 2: 在增量生成器中使用 GeneratorSyntaxContext
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var provider = context.SyntaxProvider.CreateSyntaxProvider(
predicate: static (node, _) => node is ClassDeclarationSyntax,
transform: static (ctx, _) => {
var semanticModel = ctx.SemanticModel; // 直接使用
return semanticModel.GetDeclaredSymbol(ctx.Node);
});
}Q2: GetDeclaredSymbol 和 GetSymbolInfo 有什么区别?
A:
GetDeclaredSymbol: 用于声明节点(如类声明、方法声明),返回声明的符号GetSymbolInfo: 用于引用节点(如方法调用、变量引用),返回引用的符号
csharp
// GetDeclaredSymbol - 用于声明
var classDecl = (ClassDeclarationSyntax)node;
var classSymbol = semanticModel.GetDeclaredSymbol(classDecl); // INamedTypeSymbol
// GetSymbolInfo - 用于引用
var invocation = (InvocationExpressionSyntax)node;
var symbolInfo = semanticModel.GetSymbolInfo(invocation);
var methodSymbol = symbolInfo.Symbol; // IMethodSymbolQ3: 如何比较两个符号是否相同?
A: 必须使用 SymbolEqualityComparer,不能使用 ==:
csharp
ISymbol symbol1, symbol2;
// ✅ 正确
bool areEqual = SymbolEqualityComparer.Default.Equals(symbol1, symbol2);
// ❌ 错误:不可靠
bool areEqual = symbol1 == symbol2;Q4: 如何检查类型是否实现了某个接口?
A: 使用 AllInterfaces 属性:
csharp
ITypeSymbol type;
INamedTypeSymbol interfaceType;
bool implementsInterface = type.AllInterfaces.Any(i =>
SymbolEqualityComparer.Default.Equals(i, interfaceType));Q5: 如何获取泛型类型的类型参数?
A: 区分类型参数(定义)和类型实参(使用):
csharp
INamedTypeSymbol typeSymbol;
// 类型参数(定义时):List<T> 中的 T
ImmutableArray<ITypeParameterSymbol> typeParameters = typeSymbol.TypeParameters;
// 类型实参(使用时):List<int> 中的 int
ImmutableArray<ITypeSymbol> typeArguments = typeSymbol.TypeArguments;Q6: 如何处理可空引用类型?
A: 使用 NullableAnnotation 属性:
csharp
ITypeSymbol type;
NullableAnnotation nullability = type.NullableAnnotation;
switch (nullability)
{
case NullableAnnotation.None:
// 未指定或不适用
break;
case NullableAnnotation.NotAnnotated:
// 非可空:string
break;
case NullableAnnotation.Annotated:
// 可空:string?
break;
}Q7: 如何获取特性的参数值?
A: 使用 AttributeData 的 ConstructorArguments 和 NamedArguments:
csharp
AttributeData attribute;
// 构造函数参数
foreach (var arg in attribute.ConstructorArguments)
{
object value = arg.Value;
}
// 命名参数
foreach (var namedArg in attribute.NamedArguments)
{
string name = namedArg.Key;
object value = namedArg.Value.Value;
}Q8: 如何查找方法的所有重载?
A: 使用 GetMembers 并过滤:
csharp
INamedTypeSymbol typeSymbol;
string methodName = "MyMethod";
var overloads = typeSymbol.GetMembers(methodName)
.OfType<IMethodSymbol>()
.Where(m => m.MethodKind == MethodKind.Ordinary)
.ToList();Q9: 如何检查方法是否是异步方法?
A: 使用 IsAsync 属性:
csharp
IMethodSymbol method;
bool isAsync = method.IsAsync;
// 也可以检查返回类型
bool returnsTask = method.ReturnType.Name == "Task" ||
method.ReturnType.Name == "ValueTask";Q10: 如何获取符号的完全限定名?
A: 使用 ToDisplayString 方法:
csharp
ISymbol symbol;
// 完全限定名
string fullName = symbol.ToDisplayString(
SymbolDisplayFormat.FullyQualifiedFormat);
// 最小限定名
string minimalName = symbol.ToDisplayString(
SymbolDisplayFormat.MinimallyQualifiedFormat);Q11: 如何检查类型是否是值类型或引用类型?
A: 使用 IsValueType 和 IsReferenceType 属性:
csharp
ITypeSymbol type;
bool isValueType = type.IsValueType; // struct, enum, primitive
bool isReferenceType = type.IsReferenceType; // class, interface, delegateQ12: 如何获取属性的后备字段?
A: 对于自动属性,后备字段通常命名为 <PropertyName>k__BackingField:
csharp
IPropertySymbol property;
if (property.IsAutoProperty)
{
var backingFieldName = $"<{property.Name}>k__BackingField";
var backingField = property.ContainingType.GetMembers(backingFieldName)
.OfType<IFieldSymbol>()
.FirstOrDefault();
}Q13: 如何处理扩展方法?
A: 使用 IsExtensionMethod 和 ReducedFrom 属性:
csharp
IMethodSymbol method;
// 检查是否是扩展方法
bool isExtension = method.IsExtensionMethod;
// 获取原始方法(对于 reduced 扩展方法)
IMethodSymbol originalMethod = method.ReducedFrom;
// 第一个参数是 this 参数
if (isExtension && method.Parameters.Length > 0)
{
IParameterSymbol thisParameter = method.Parameters[0];
bool isThis = thisParameter.IsThis; // true
}Q14: 如何检查类型是否是泛型类型?
A: 使用 IsGenericType 和相关属性:
csharp
INamedTypeSymbol type;
// 是否是泛型类型
bool isGeneric = type.IsGenericType;
// 是否是未绑定的泛型类型(如 List<>)
bool isUnbound = type.IsUnboundGenericType;
// 泛型参数数量
int arity = type.Arity;
// 原始泛型定义
INamedTypeSymbol originalDefinition = type.ConstructedFrom;Q15: 如何在增量生成器中高效使用 SemanticModel?
A: 使用 GeneratorSyntaxContext 提供的 SemanticModel:
csharp
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var provider = context.SyntaxProvider.CreateSyntaxProvider(
predicate: static (node, _) => node is ClassDeclarationSyntax,
transform: static (ctx, _) => {
// ✅ 使用 ctx.SemanticModel,已经缓存
var symbol = ctx.SemanticModel.GetDeclaredSymbol(ctx.Node);
// ❌ 不要自己创建 SemanticModel
// var model = compilation.GetSemanticModel(tree);
return symbol;
});
}🔑 关键要点
- 性能优化: 缓存 SemanticModel,避免重复调用
- 符号比较: 必须使用 SymbolEqualityComparer
- 空值处理: 始终检查符号和类型是否为 null
- 类型检查: 使用 SpecialType 和 TypeKind 检查类型
- 避免反模式: 不要使用 ==、字符串比较、忽略空值
📖 相关文档
🚀 下一步
- 探索实际应用场景
- 学习高级符号操作
- 编写高质量的源生成器
最后更新: 2026-02-05