Skip to content

语义模型最佳实践

📚 文档导航

本文档介绍语义模型的最佳实践、性能优化技巧和常见问题解答。

📖 文档系列

文档内容难度
语义模型基础基础概念、快速入门、核心功能🟢 入门
类型信息获取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.Class

Q2: 如何检查两个类型是否相同?

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.GetMethodIPropertySymbol.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 参考

实战示例

官方文档


最后更新: 2025-01-21

基于 MIT 许可发布