Skip to content

语义模型

📚 文档导航

本文档是语义模型系列的主文档,介绍基础概念和快速入门。

📖 文档系列

文档内容难度
语义模型基础基础概念、快速入门、核心功能🟢 入门
类型信息获取GetTypeInfo、常量值、TypeInfo 详解🟡 中级
符号信息获取GetDeclaredSymbol、GetSymbolInfo、符号遍历🟡 中级
高级主题泛型、继承、特性、类型转换🔴 高级
最佳实践性能优化、常见问题、实战场景🟡 中级

什么是语义模型?

语义模型(Semantic Model)提供代码的语义信息,包括类型、符号、绑定等。

💡 核心概念

语义模型是连接语法树类型系统的桥梁,它让你能够:

  • 获取表达式的类型信息
  • 查找符号的定义和引用
  • 检查类型兼容性和转换
  • 分析继承关系和接口实现

语法树 vs 语义模型

特性语法树语义模型
内容代码结构类型信息
信息语法正确性语义正确性
示例Person 是标识符Person 是类类型
性能快速较慢(需要解析)
依赖需要编译单元

⚠️ 重要区别

  • 语法树:只知道代码的结构,不知道 Person 是什么
  • 语义模型:知道 Person 是一个类,它的成员,继承关系等

快速开始

获取语义模型

csharp
// 从生成器上下文获取
var compilation = context.Compilation;
var syntaxTree = compilation.SyntaxTrees.First();
var semanticModel = compilation.GetSemanticModel(syntaxTree);

💡 提示

每个语法树都有对应的语义模型,建议缓存语义模型以提高性能。

完整示例

csharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

public class SemanticModelExample
{
    public void Demonstrate()
    {
        // 1. 准备源代码
        var code = @"
            using System;
            
            namespace MyApp
            {
                public class Person
                {
                    public string Name { get; set; }
                    public int Age { get; set; }
                    
                    public void SayHello()
                    {
                        Console.WriteLine($""Hello, I'm {Name}"");
                    }
                }
            }
        ";
        
        // 2. 创建语法树
        var syntaxTree = CSharpSyntaxTree.ParseText(code);
        
        // 3. 创建编译单元(需要引用)
        var compilation = CSharpCompilation.Create("MyAssembly")
            .AddReferences(
                MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
                MetadataReference.CreateFromFile(typeof(Console).Assembly.Location)
            )
            .AddSyntaxTrees(syntaxTree);
        
        // 4. 获取语义模型
        var semanticModel = compilation.GetSemanticModel(syntaxTree);
        
        // 5. 使用语义模型
        var root = syntaxTree.GetRoot();
        var classDecl = root.DescendantNodes()
            .OfType<ClassDeclarationSyntax>()
            .First();
        
        var classSymbol = semanticModel.GetDeclaredSymbol(classDecl);
        Console.WriteLine($"类名: {classSymbol.Name}");
        Console.WriteLine($"命名空间: {classSymbol.ContainingNamespace}");
    }
}

核心功能

1. 获取符号信息

从语法节点获取对应的符号(Symbol)。

csharp
// 从语法节点获取符号
var classDeclaration = root.DescendantNodes()
    .OfType<ClassDeclarationSyntax>()
    .First();

var symbol = semanticModel.GetDeclaredSymbol(classDeclaration);
Console.WriteLine($"完整名称: {symbol.ToDisplayString()}");

📚 深入学习

详细了解符号信息获取,请查看 符号信息获取详解

2. 获取类型信息

获取表达式的类型信息。

csharp
// 获取表达式的类型
var expression = node.DescendantNodes()
    .OfType<ExpressionSyntax>()
    .First();

var typeInfo = semanticModel.GetTypeInfo(expression);
Console.WriteLine($"类型: {typeInfo.Type.Name}");

📚 深入学习

详细了解类型信息获取,请查看 类型信息获取详解

3. 查找符号

在编译单元中查找特定的符号。

csharp
// 查找特定类型
var personType = compilation.GetTypeByMetadataName("MyNamespace.Person");

// 查找基类
var baseType = personType.BaseType;

// 查找接口
var interfaces = personType.Interfaces;

符号类型层次结构

语义模型中的符号遵循以下层次结构:

常用符号类型

INamedTypeSymbol(类、结构、接口)

csharp
var typeSymbol = semanticModel.GetDeclaredSymbol(classDeclaration) 
    as INamedTypeSymbol;

// 获取成员
var properties = typeSymbol.GetMembers()
    .OfType<IPropertySymbol>();

// 检查类型
bool isClass = typeSymbol.TypeKind == TypeKind.Class;
bool isPublic = typeSymbol.DeclaredAccessibility == Accessibility.Public;

IMethodSymbol(方法)

csharp
var methodSymbol = semanticModel.GetDeclaredSymbol(methodDeclaration) 
    as IMethodSymbol;

// 获取参数
var parameters = methodSymbol.Parameters;

// 获取返回类型
var returnType = methodSymbol.ReturnType;

// 检查是否异步
bool isAsync = methodSymbol.IsAsync;

IPropertySymbol(属性)

csharp
var propertySymbol = semanticModel.GetDeclaredSymbol(propertyDeclaration) 
    as IPropertySymbol;

// 获取类型
var type = propertySymbol.Type;

// 检查访问器
bool hasGetter = propertySymbol.GetMethod != null;
bool hasSetter = propertySymbol.SetMethod != null;

实际应用示例

检查类是否实现接口

csharp
bool ImplementsInterface(INamedTypeSymbol typeSymbol, string interfaceName)
{
    return typeSymbol.AllInterfaces.Any(i => 
        i.ToDisplayString() == interfaceName);
}

获取所有公共属性

csharp
var publicProperties = typeSymbol.GetMembers()
    .OfType<IPropertySymbol>()
    .Where(p => p.DeclaredAccessibility == Accessibility.Public);

检查类型兼容性

csharp
var conversion = semanticModel.ClassifyConversion(
    sourceExpression, 
    targetType);

bool canConvert = conversion.Exists && !conversion.IsIdentity;

在源生成器中使用

csharp
public void Execute(GeneratorExecutionContext context)
{
    foreach (var syntaxTree in context.Compilation.SyntaxTrees)
    {
        var semanticModel = context.Compilation.GetSemanticModel(syntaxTree);
        var root = syntaxTree.GetRoot();
        
        var classes = root.DescendantNodes()
            .OfType<ClassDeclarationSyntax>();
        
        foreach (var cls in classes)
        {
            var symbol = semanticModel.GetDeclaredSymbol(cls) 
                as INamedTypeSymbol;
            
            if (symbol == null) continue;
            
            // 检查是否有特定特性
            var hasAttribute = symbol.GetAttributes()
                .Any(a => a.AttributeClass?.Name == "MyAttribute");
            
            if (hasAttribute)
            {
                // 生成代码...
            }
        }
    }
}

语义模型的工作流程


性能考虑

✅ 推荐做法

csharp
// 缓存语义模型
var semanticModel = compilation.GetSemanticModel(syntaxTree);

// 批量处理
var symbols = nodes.Select(n => semanticModel.GetDeclaredSymbol(n));

❌ 避免

csharp
// 不要重复获取语义模型
foreach (var node in nodes)
{
    var model = compilation.GetSemanticModel(syntaxTree); // 慢!
    var symbol = model.GetDeclaredSymbol(node);
}

⚠️ 性能提示

语义模型的创建相对昂贵,应该缓存并重用。详细的性能优化技巧请查看 最佳实践


下一步学习

📚 深入学习语义模型

🔗 相关主题

📖 API 参考

💡 实战示例


🔗 外部资源


最后更新: 2025-01-21

基于 MIT 许可发布