Skip to content

最佳实践和常见问题

语义模型 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; // IMethodSymbol

Q3: 如何比较两个符号是否相同?

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: 使用 AttributeDataConstructorArgumentsNamedArguments

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: 使用 IsValueTypeIsReferenceType 属性:

csharp
ITypeSymbol type;

bool isValueType = type.IsValueType;      // struct, enum, primitive
bool isReferenceType = type.IsReferenceType; // class, interface, delegate

Q12: 如何获取属性的后备字段?

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: 使用 IsExtensionMethodReducedFrom 属性:

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;
        });
}

🔑 关键要点

  1. 性能优化: 缓存 SemanticModel,避免重复调用
  2. 符号比较: 必须使用 SymbolEqualityComparer
  3. 空值处理: 始终检查符号和类型是否为 null
  4. 类型检查: 使用 SpecialType 和 TypeKind 检查类型
  5. 避免反模式: 不要使用 ==、字符串比较、忽略空值

📖 相关文档


🚀 下一步

  • 探索实际应用场景
  • 学习高级符号操作
  • 编写高质量的源生成器

最后更新: 2026-02-05

基于 MIT 许可发布