Skip to content

语义模型基础

⏱️ 10-15 分钟 | 📚 基础级别 | 前置知识:编译基础

🎯 学习目标

完成本文档后,你将能够:

  • [ ] 理解语义模型与语法树的区别
  • [ ] 获取和使用语义模型
  • [ ] 获取符号的类型信息
  • [ ] 查找和分析类、方法、属性等符号
  • [ ] 处理特性(Attribute)信息

📖 核心概念

什么是语义模型?

语义模型(Semantic Model) 提供代码的语义信息,包括类型、符号、作用域等。与语法树只关注代码结构不同,语义模型关注代码的含义

语法树 vs 语义模型

特性语法树语义模型
关注点代码结构代码含义
提供信息类名、方法名、语法结构类型、符号、作用域、重载解析
示例ClassDeclarationSyntaxINamedTypeSymbol
使用场景查找语法节点、代码格式化类型检查、符号查找、语义分析

简单理解

  • 语法树告诉你"这是一个类声明"
  • 语义模型告诉你"这个类叫什么名字、继承自哪个类、有哪些成员"

核心概念

概念说明示例
SemanticModel提供语义信息的模型compilation.GetSemanticModel(tree)
ISymbol代码元素的语义表示类、方法、属性等
ITypeSymbol类型的语义表示int, string, MyClass
Accessibility访问修饰符Public, Private, Internal
SymbolKind符号类型NamedType, Method, Property

🔧 常用方法

方法 1:获取语义模型

用途:从编译对象获取语义模型

使用场景:需要分析代码语义时的第一步

csharp
// 从编译获取语义模型
Compilation compilation = /* ... */;
SyntaxTree syntaxTree = /* ... */;
SemanticModel semanticModel = compilation.GetSemanticModel(syntaxTree);

在增量生成器中使用

csharp
public void Initialize(IncrementalGeneratorInitializationContext context)
{
    var provider = context.SyntaxProvider.CreateSyntaxProvider(
        predicate: static (node, _) => node is ClassDeclarationSyntax,
        transform: static (ctx, _) => {
            // ctx.SemanticModel 就是语义模型
            var semanticModel = ctx.SemanticModel;
            var classSymbol = semanticModel.GetDeclaredSymbol(ctx.Node);
            return classSymbol;
        });
}

方法 2:获取声明的符号

用途:从声明节点获取符号信息

使用场景:分析类、方法、属性等声明

csharp
// 获取类符号
var classDecl = (ClassDeclarationSyntax)node;
var classSymbol = semanticModel.GetDeclaredSymbol(classDecl) as INamedTypeSymbol;

// 获取方法符号
var methodDecl = (MethodDeclarationSyntax)node;
var methodSymbol = semanticModel.GetDeclaredSymbol(methodDecl) as IMethodSymbol;

// 获取属性符号
var propertyDecl = (PropertyDeclarationSyntax)node;
var propertySymbol = semanticModel.GetDeclaredSymbol(propertyDecl) as IPropertySymbol;

方法 3:获取类型信息

用途:获取表达式的类型

使用场景:需要知道变量、表达式的类型时

csharp
// 获取变量类型
// var x = 42;
var variableDecl = (VariableDeclaratorSyntax)node;
var typeInfo = semanticModel.GetTypeInfo(variableDecl.Initializer.Value);
// typeInfo.Type = System.Int32

// 获取表达式类型
TypeInfo typeInfo = semanticModel.GetTypeInfo(expression);
ITypeSymbol type = typeInfo.Type;              // 表达式的类型
ITypeSymbol convertedType = typeInfo.ConvertedType; // 转换后的类型

方法 4:获取符号信息(用于引用)

用途:获取表达式引用的符号

使用场景:分析方法调用、变量引用等

csharp
// 获取方法调用的符号
// MyMethod();
var invocation = (InvocationExpressionSyntax)node;
var symbolInfo = semanticModel.GetSymbolInfo(invocation);
var methodSymbol = symbolInfo.Symbol as IMethodSymbol;

方法 5:检查特性

用途:检查符号是否有特定特性

使用场景:基于特性的代码生成

csharp
// 检查类是否有特定特性
INamedTypeSymbol classSymbol = /* ... */;
var attributes = classSymbol.GetAttributes();

bool hasAttribute = attributes.Any(a => 
    a.AttributeClass?.Name == "MyAttribute" ||
    a.AttributeClass?.Name == "MyAttributeAttribute");

💡 实践示例

示例 1:分析类的基本信息

场景:提取类的名称、命名空间、访问修饰符等基本信息

csharp
public class ClassInfoExtractor
{
    public ClassInfo ExtractClassInfo(
        ClassDeclarationSyntax classDecl,
        SemanticModel semanticModel)
    {
        // 1. 获取类符号
        var classSymbol = semanticModel.GetDeclaredSymbol(classDecl) 
            as INamedTypeSymbol;
        
        if (classSymbol == null)
            return null;
        
        // 2. 提取基本信息
        return new ClassInfo
        {
            Name = classSymbol.Name,
            FullName = classSymbol.ToDisplayString(),
            Namespace = classSymbol.ContainingNamespace.ToDisplayString(),
            IsAbstract = classSymbol.IsAbstract,
            IsSealed = classSymbol.IsSealed,
            IsStatic = classSymbol.IsStatic,
            Accessibility = classSymbol.DeclaredAccessibility.ToString()
        };
    }
}

public class ClassInfo
{
    public string Name { get; set; }
    public string FullName { get; set; }
    public string Namespace { get; set; }
    public bool IsAbstract { get; set; }
    public bool IsSealed { get; set; }
    public bool IsStatic { get; set; }
    public string Accessibility { get; set; }
}

关键点

  • 使用 GetDeclaredSymbol 获取类符号
  • ToDisplayString() 获取完全限定名
  • ContainingNamespace 获取命名空间
  • 各种 Is* 属性检查修饰符

示例 2:查找类的所有公共方法

场景:分析类的公共方法,用于生成文档或代理类

csharp
public class PublicMethodFinder
{
    public List<MethodInfo> FindPublicMethods(
        INamedTypeSymbol classSymbol)
    {
        var methods = new List<MethodInfo>();
        
        // 获取所有成员
        foreach (var member in classSymbol.GetMembers())
        {
            // 只处理公共的普通方法
            if (member is IMethodSymbol method &&
                method.MethodKind == MethodKind.Ordinary &&
                method.DeclaredAccessibility == Accessibility.Public)
            {
                methods.Add(new MethodInfo
                {
                    Name = method.Name,
                    ReturnType = method.ReturnType.ToDisplayString(),
                    Parameters = method.Parameters
                        .Select(p => $"{p.Type.ToDisplayString()} {p.Name}")
                        .ToList()
                });
            }
        }
        
        return methods;
    }
}

public class MethodInfo
{
    public string Name { get; set; }
    public string ReturnType { get; set; }
    public List<string> Parameters { get; set; }
}

关键点

  • GetMembers() 获取所有成员
  • MethodKind.Ordinary 排除构造函数、属性访问器等
  • DeclaredAccessibility 检查访问修饰符
  • Parameters 获取参数列表

示例 3:检查类是否有特定特性

场景:基于特性的代码生成,只处理带有特定特性的类

csharp
public class AttributeChecker
{
    public bool HasGenerateAttribute(
        INamedTypeSymbol classSymbol,
        string attributeName)
    {
        // 获取所有特性
        var attributes = classSymbol.GetAttributes();
        
        // 检查是否有指定特性(支持带或不带 "Attribute" 后缀)
        return attributes.Any(a => 
            a.AttributeClass?.Name == attributeName ||
            a.AttributeClass?.Name == attributeName + "Attribute");
    }
    
    public AttributeInfo GetAttributeInfo(
        INamedTypeSymbol classSymbol,
        string attributeName)
    {
        // 获取特定特性
        var attribute = classSymbol.GetAttributes()
            .FirstOrDefault(a => 
                a.AttributeClass?.Name == attributeName ||
                a.AttributeClass?.Name == attributeName + "Attribute");
        
        if (attribute == null)
            return null;
        
        // 提取构造函数参数
        var ctorArgs = attribute.ConstructorArguments
            .Select(arg => arg.Value)
            .ToList();
        
        // 提取命名参数
        var namedArgs = attribute.NamedArguments
            .ToDictionary(
                kvp => kvp.Key,
                kvp => kvp.Value.Value);
        
        return new AttributeInfo
        {
            Name = attribute.AttributeClass.Name,
            ConstructorArguments = ctorArgs,
            NamedArguments = namedArgs
        };
    }
}

public class AttributeInfo
{
    public string Name { get; set; }
    public List<object> ConstructorArguments { get; set; }
    public Dictionary<string, object> NamedArguments { get; set; }
}

关键点

  • GetAttributes() 获取所有特性
  • 支持带或不带 "Attribute" 后缀的名称
  • ConstructorArguments 获取构造函数参数
  • NamedArguments 获取命名参数

📋 常用符号类型

ISymbol - 所有符号的基接口

csharp
ISymbol symbol;

// 基本属性
string name = symbol.Name;                    // 符号名称
SymbolKind kind = symbol.Kind;                // 符号类型
Accessibility accessibility = symbol.DeclaredAccessibility; // 访问修饰符

// 修饰符检查
bool isStatic = symbol.IsStatic;
bool isVirtual = symbol.IsVirtual;
bool isAbstract = symbol.IsAbstract;

// 包含关系
INamedTypeSymbol containingType = symbol.ContainingType;
INamespaceSymbol containingNamespace = symbol.ContainingNamespace;

// 特性
ImmutableArray<AttributeData> attributes = symbol.GetAttributes();

// 格式化输出
string displayString = symbol.ToDisplayString();

INamedTypeSymbol - 类、结构、接口等

csharp
INamedTypeSymbol typeSymbol;

// 类型信息
TypeKind typeKind = typeSymbol.TypeKind;      // Class, Struct, Interface, Enum
bool isReferenceType = typeSymbol.IsReferenceType;
bool isValueType = typeSymbol.IsValueType;

// 继承和接口
INamedTypeSymbol baseType = typeSymbol.BaseType;
ImmutableArray<INamedTypeSymbol> interfaces = typeSymbol.Interfaces;

// 成员访问
ImmutableArray<ISymbol> members = typeSymbol.GetMembers();
ImmutableArray<ISymbol> namedMembers = typeSymbol.GetMembers("MethodName");

IMethodSymbol - 方法

csharp
IMethodSymbol methodSymbol;

// 方法信息
ITypeSymbol returnType = methodSymbol.ReturnType;
bool returnsVoid = methodSymbol.ReturnsVoid;
ImmutableArray<IParameterSymbol> parameters = methodSymbol.Parameters;

// 方法特性
bool isAsync = methodSymbol.IsAsync;
bool isExtensionMethod = methodSymbol.IsExtensionMethod;
MethodKind methodKind = methodSymbol.MethodKind;

IPropertySymbol - 属性

csharp
IPropertySymbol propertySymbol;

// 属性信息
ITypeSymbol type = propertySymbol.Type;
bool isReadOnly = propertySymbol.IsReadOnly;

// 访问器
IMethodSymbol getMethod = propertySymbol.GetMethod;
IMethodSymbol setMethod = propertySymbol.SetMethod;

⚠️ 常见陷阱

陷阱 1:符号比较

错误做法

csharp
// 不要使用 == 或 Equals 比较符号
if (symbol1 == symbol2) { }
if (symbol1.Equals(symbol2)) { }

正确做法

csharp
// 使用 SymbolEqualityComparer
if (SymbolEqualityComparer.Default.Equals(symbol1, symbol2)) { }

// 在集合中使用
var symbolSet = new HashSet<ISymbol>(SymbolEqualityComparer.Default);

陷阱 2:空引用检查

错误做法

csharp
var classSymbol = semanticModel.GetDeclaredSymbol(classDecl);
string name = classSymbol.Name; // 可能抛出 NullReferenceException

正确做法

csharp
var classSymbol = semanticModel.GetDeclaredSymbol(classDecl);
if (classSymbol == null)
    return;

string name = classSymbol.Name;

陷阱 3:特性名称匹配

错误做法

csharp
// 只检查一种形式
bool hasAttr = attributes.Any(a => a.AttributeClass?.Name == "MyAttribute");

正确做法

csharp
// 同时检查带和不带 "Attribute" 后缀
bool hasAttr = attributes.Any(a => 
    a.AttributeClass?.Name == "MyAttribute" ||
    a.AttributeClass?.Name == "MyAttributeAttribute");

🎓 快速参考

获取语义信息的方法

方法用途返回类型
GetDeclaredSymbol(node)获取声明的符号ISymbol
GetTypeInfo(expression)获取表达式类型TypeInfo
GetSymbolInfo(expression)获取引用的符号SymbolInfo
GetConstantValue(expression)获取常量值Optional<object>

符号类型层次

ISymbol (基接口)
├── INamespaceSymbol (命名空间)
├── ITypeSymbol (类型)
│   ├── INamedTypeSymbol (类、结构、接口、枚举)
│   └── IArrayTypeSymbol (数组类型)
├── IMethodSymbol (方法)
├── IPropertySymbol (属性)
├── IFieldSymbol (字段)
└── IParameterSymbol (参数)

⏭️ 下一步

完成基础学习后,你可以:

🔗 相关资源


最后更新: 2025-02-05

基于 MIT 许可发布