Skip to content

事件详解

简介

本文档详细介绍 IEventSymbol(事件符号),包括事件访问器、事件类型和委托、事件重写和实现。

适合人群: 初级到中级开发者
预计阅读时间: 25 分钟
前置知识: 语法树基础、符号系统、委托和事件概念


📋 本文导航

章节难度阅读时间链接
核心属性🟢8 分钟查看
事件访问器🟢8 分钟查看
事件类型和委托🟢5 分钟查看
事件重写和实现🟢4 分钟查看

返回: 主文档 | 上一篇: 命名空间详解 | 下一篇: 类型参数详解


🟢 核心属性

IEventSymbol 表示事件成员,事件是 C# 中实现观察者模式的核心机制。

基本信息属性

csharp
IEventSymbol eventSymbol;

// 事件名称
string name = eventSymbol.Name;

// 事件类型(委托类型)
ITypeSymbol eventType = eventSymbol.Type;

// 访问修饰符
Accessibility accessibility = eventSymbol.DeclaredAccessibility;

// 是否是静态事件
bool isStatic = eventSymbol.IsStatic;

// 是否是抽象事件
bool isAbstract = eventSymbol.IsAbstract;

// 是否是虚事件
bool isVirtual = eventSymbol.IsVirtual;

// 是否是重写事件
bool isOverride = eventSymbol.IsOverride;

// 是否是密封事件
bool isSealed = eventSymbol.IsSealed;

// 所属类型
INamedTypeSymbol containingType = eventSymbol.ContainingType;

访问器属性

csharp
// add 访问器(订阅事件时调用)
IMethodSymbol addMethod = eventSymbol.AddMethod;

// remove 访问器(取消订阅事件时调用)
IMethodSymbol removeMethod = eventSymbol.RemoveMethod;

// raise 访问器(WinRT 事件特有,通常为 null)
IMethodSymbol raiseMethod = eventSymbol.RaiseMethod;

示例:分析事件基本信息

csharp
public void AnalyzeEventBasicInfo(IEventSymbol eventSymbol)
{
    Console.WriteLine($"事件名称: {eventSymbol.Name}");
    Console.WriteLine($"事件类型: {eventSymbol.Type.ToDisplayString()}");
    Console.WriteLine($"访问修饰符: {eventSymbol.DeclaredAccessibility}");
    Console.WriteLine($"所属类型: {eventSymbol.ContainingType.Name}");
    
    // 检查事件特性
    if (eventSymbol.IsStatic)
        Console.WriteLine("这是一个静态事件");
    if (eventSymbol.IsAbstract)
        Console.WriteLine("这是一个抽象事件");
    if (eventSymbol.IsVirtual)
        Console.WriteLine("这是一个虚事件");
    if (eventSymbol.IsOverride)
        Console.WriteLine("这是一个重写事件");
    
    // 检查访问器
    Console.WriteLine($"\n访问器:");
    Console.WriteLine($"  add 方法: {(eventSymbol.AddMethod != null ? "存在" : "不存在")}");
    Console.WriteLine($"  remove 方法: {(eventSymbol.RemoveMethod != null ? "存在" : "不存在")}");
    
    if (eventSymbol.AddMethod != null)
    {
        Console.WriteLine($"  add 访问修饰符: {eventSymbol.AddMethod.DeclaredAccessibility}");
    }
    
    if (eventSymbol.RemoveMethod != null)
    {
        Console.WriteLine($"  remove 访问修饰符: {eventSymbol.RemoveMethod.DeclaredAccessibility}");
    }
}

提示

事件的 Type 属性返回委托类型,如 EventHandler<T> 或自定义委托类型。


🟢 事件访问器

事件有两个主要访问器:addremove,分别用于订阅和取消订阅事件。

分析 add 访问器

csharp
/// <summary>
/// 分析事件的 add 访问器
/// </summary>
public void AnalyzeAddAccessor(IEventSymbol eventSymbol)
{
    var addMethod = eventSymbol.AddMethod;
    
    if (addMethod == null)
    {
        Console.WriteLine("事件没有 add 访问器");
        return;
    }
    
    Console.WriteLine($"add 访问器:");
    Console.WriteLine($"  方法名称: {addMethod.Name}");
    Console.WriteLine($"  访问修饰符: {addMethod.DeclaredAccessibility}");
    Console.WriteLine($"  是否是编译器生成: {addMethod.IsImplicitlyDeclared}");
    
    // 分析参数
    if (addMethod.Parameters.Length > 0)
    {
        var param = addMethod.Parameters[0];
        Console.WriteLine($"  参数类型: {param.Type.ToDisplayString()}");
        Console.WriteLine($"  参数名称: {param.Name}");
    }
}

分析 remove 访问器

csharp
/// <summary>
/// 分析事件的 remove 访问器
/// </summary>
public void AnalyzeRemoveAccessor(IEventSymbol eventSymbol)
{
    var removeMethod = eventSymbol.RemoveMethod;
    
    if (removeMethod == null)
    {
        Console.WriteLine("事件没有 remove 访问器");
        return;
    }
    
    Console.WriteLine($"remove 访问器:");
    Console.WriteLine($"  方法名称: {removeMethod.Name}");
    Console.WriteLine($"  访问修饰符: {removeMethod.DeclaredAccessibility}");
    Console.WriteLine($"  是否是编译器生成: {removeMethod.IsImplicitlyDeclared}");
    
    // 分析参数
    if (removeMethod.Parameters.Length > 0)
    {
        var param = removeMethod.Parameters[0];
        Console.WriteLine($"  参数类型: {param.Type.ToDisplayString()}");
        Console.WriteLine($"  参数名称: {param.Name}");
    }
}

字段式事件 vs 属性式事件

C# 中有两种事件声明方式:

字段式事件(编译器自动生成访问器):

csharp
public event EventHandler MyEvent;

属性式事件(手动实现访问器):

csharp
private EventHandler _myEvent;
public event EventHandler MyEvent
{
    add { _myEvent += value; }
    remove { _myEvent -= value; }
}
csharp
/// <summary>
/// 判断是字段式事件还是属性式事件
/// </summary>
public void AnalyzeEventStyle(IEventSymbol eventSymbol)
{
    if (eventSymbol.AddMethod != null && eventSymbol.AddMethod.IsImplicitlyDeclared)
    {
        Console.WriteLine("这是字段式事件(编译器自动生成访问器)");
    }
    else
    {
        Console.WriteLine("这是属性式事件(手动实现访问器)");
    }
}
点击查看完整示例:事件访问器分析器
csharp
/// <summary>
/// 完整的事件访问器分析器
/// </summary>
public class EventAccessorAnalyzer
{
    public void AnalyzeEvent(IEventSymbol eventSymbol)
    {
        Console.WriteLine($"分析事件: {eventSymbol.Name}");
        Console.WriteLine($"事件类型: {eventSymbol.Type.ToDisplayString()}");
        Console.WriteLine();
        
        // 1. 判断事件样式
        bool isFieldLike = eventSymbol.AddMethod?.IsImplicitlyDeclared ?? false;
        Console.WriteLine($"事件样式: {(isFieldLike ? "字段式" : "属性式")}");
        Console.WriteLine();
        
        // 2. 分析 add 访问器
        if (eventSymbol.AddMethod != null)
        {
            Console.WriteLine("add 访问器:");
            AnalyzeAccessor(eventSymbol.AddMethod);
        }
        else
        {
            Console.WriteLine("add 访问器: 不存在");
        }
        Console.WriteLine();
        
        // 3. 分析 remove 访问器
        if (eventSymbol.RemoveMethod != null)
        {
            Console.WriteLine("remove 访问器:");
            AnalyzeAccessor(eventSymbol.RemoveMethod);
        }
        else
        {
            Console.WriteLine("remove 访问器: 不存在");
        }
        Console.WriteLine();
        
        // 4. 检查访问器的可访问性
        if (eventSymbol.AddMethod != null && eventSymbol.RemoveMethod != null)
        {
            var addAccessibility = eventSymbol.AddMethod.DeclaredAccessibility;
            var removeAccessibility = eventSymbol.RemoveMethod.DeclaredAccessibility;
            
            if (addAccessibility != removeAccessibility)
            {
                Console.WriteLine("警告: add 和 remove 访问器的访问修饰符不同");
                Console.WriteLine($"  add: {addAccessibility}");
                Console.WriteLine($"  remove: {removeAccessibility}");
            }
        }
    }
    
    private void AnalyzeAccessor(IMethodSymbol accessor)
    {
        Console.WriteLine($"  方法名称: {accessor.Name}");
        Console.WriteLine($"  访问修饰符: {accessor.DeclaredAccessibility}");
        Console.WriteLine($"  是否是编译器生成: {accessor.IsImplicitlyDeclared}");
        Console.WriteLine($"  返回类型: {accessor.ReturnType.ToDisplayString()}");
        
        if (accessor.Parameters.Length > 0)
        {
            Console.WriteLine("  参数:");
            foreach (var param in accessor.Parameters)
            {
                Console.WriteLine($"    - {param.Type.ToDisplayString()} {param.Name}");
            }
        }
    }
}

关键要点:

  • 字段式事件的访问器是编译器自动生成的(IsImplicitlyDeclaredtrue
  • 属性式事件的访问器是手动实现的(IsImplicitlyDeclaredfalse
  • add 和 remove 访问器通常有相同的访问修饰符
  • 访问器的参数类型与事件类型相同

🟢 事件类型和委托

事件的类型必须是委托类型。

分析事件的委托类型

csharp
/// <summary>
/// 分析事件的委托类型
/// </summary>
public void AnalyzeEventDelegateType(IEventSymbol eventSymbol)
{
    var delegateType = eventSymbol.Type as INamedTypeSymbol;
    
    if (delegateType == null)
    {
        Console.WriteLine("事件类型不是命名类型");
        return;
    }
    
    Console.WriteLine($"事件: {eventSymbol.Name}");
    Console.WriteLine($"委托类型: {delegateType.ToDisplayString()}");
    Console.WriteLine($"委托种类: {delegateType.TypeKind}");
    
    // 检查是否是委托类型
    if (delegateType.TypeKind == TypeKind.Delegate)
    {
        Console.WriteLine("这是一个委托类型");
        
        // 获取委托的 Invoke 方法
        var invokeMethod = delegateType.DelegateInvokeMethod;
        if (invokeMethod != null)
        {
            Console.WriteLine($"\n委托签名:");
            Console.WriteLine($"  返回类型: {invokeMethod.ReturnType.ToDisplayString()}");
            Console.WriteLine($"  参数:");
            
            foreach (var param in invokeMethod.Parameters)
            {
                Console.WriteLine($"    - {param.Type.ToDisplayString()} {param.Name}");
            }
        }
    }
}

检查常见的事件委托类型

csharp
/// <summary>
/// 检查是否使用了标准的 EventHandler 委托
/// </summary>
public void CheckStandardEventHandler(IEventSymbol eventSymbol, Compilation compilation)
{
    var eventType = eventSymbol.Type as INamedTypeSymbol;
    if (eventType == null) return;
    
    // 获取 EventHandler 和 EventHandler<T> 的符号
    var eventHandlerType = compilation.GetTypeByMetadataName("System.EventHandler");
    var genericEventHandlerType = compilation.GetTypeByMetadataName("System.EventHandler`1");
    
    // 检查是否是 EventHandler
    if (SymbolEqualityComparer.Default.Equals(eventType, eventHandlerType))
    {
        Console.WriteLine("使用标准的 EventHandler 委托");
    }
    // 检查是否是 EventHandler<T>
    else if (eventType.IsGenericType && 
             SymbolEqualityComparer.Default.Equals(
                 eventType.OriginalDefinition, genericEventHandlerType))
    {
        var typeArg = eventType.TypeArguments[0];
        Console.WriteLine($"使用泛型 EventHandler<{typeArg.ToDisplayString()}> 委托");
    }
    else
    {
        Console.WriteLine($"使用自定义委托: {eventType.ToDisplayString()}");
    }
}

生成事件处理器签名

csharp
/// <summary>
/// 生成事件处理器的方法签名
/// </summary>
public string GenerateEventHandlerSignature(IEventSymbol eventSymbol)
{
    var delegateType = eventSymbol.Type as INamedTypeSymbol;
    if (delegateType == null || delegateType.DelegateInvokeMethod == null)
    {
        return null;
    }
    
    var invokeMethod = delegateType.DelegateInvokeMethod;
    var sb = new StringBuilder();
    
    // 生成方法签名
    sb.Append($"private void On{eventSymbol.Name}(");
    
    var parameters = invokeMethod.Parameters
        .Select(p => $"{p.Type.ToDisplayString()} {p.Name}");
    
    sb.Append(string.Join(", ", parameters));
    sb.Append(")");
    
    return sb.ToString();
}

// 使用示例
public void Example(IEventSymbol eventSymbol)
{
    var signature = GenerateEventHandlerSignature(eventSymbol);
    Console.WriteLine($"建议的事件处理器签名:");
    Console.WriteLine(signature);
    Console.WriteLine("{");
    Console.WriteLine("    // 事件处理逻辑");
    Console.WriteLine("}");
}

🟢 事件重写和实现

事件可以是虚的、抽象的或重写的。

分析事件重写

csharp
/// <summary>
/// 分析事件重写关系
/// </summary>
public void AnalyzeEventOverride(IEventSymbol eventSymbol)
{
    if (!eventSymbol.IsOverride)
    {
        Console.WriteLine("这不是重写事件");
        return;
    }
    
    Console.WriteLine($"重写事件: {eventSymbol.Name}");
    
    // 获取被重写的事件
    var overriddenEvent = eventSymbol.OverriddenEvent;
    if (overriddenEvent != null)
    {
        Console.WriteLine($"被重写的事件: {overriddenEvent.Name}");
        Console.WriteLine($"定义在: {overriddenEvent.ContainingType.ToDisplayString()}");
        
        // 检查事件类型是否相同
        if (SymbolEqualityComparer.Default.Equals(
            eventSymbol.Type, overriddenEvent.Type))
        {
            Console.WriteLine("事件类型相同");
        }
        else
        {
            Console.WriteLine("警告: 事件类型不同");
        }
    }
    
    // 追溯到原始虚事件
    var current = eventSymbol;
    while (current.OverriddenEvent != null)
    {
        current = current.OverriddenEvent;
    }
    
    if (current.IsVirtual)
    {
        Console.WriteLine($"\n原始虚事件:");
        Console.WriteLine($"  名称: {current.Name}");
        Console.WriteLine($"  定义在: {current.ContainingType.ToDisplayString()}");
    }
}

查找接口事件的实现

csharp
/// <summary>
/// 查找接口事件的实现
/// </summary>
public void FindInterfaceEventImplementation(
    INamedTypeSymbol typeSymbol,
    IEventSymbol interfaceEvent)
{
    Console.WriteLine($"查找接口事件 {interfaceEvent.Name} 的实现");
    Console.WriteLine($"接口: {interfaceEvent.ContainingType.ToDisplayString()}");
    Console.WriteLine($"类型: {typeSymbol.ToDisplayString()}");
    Console.WriteLine();
    
    // 查找实现
    var implementation = typeSymbol.FindImplementationForInterfaceMember(interfaceEvent);
    
    if (implementation is IEventSymbol eventImpl)
    {
        Console.WriteLine($"找到实现:");
        Console.WriteLine($"  事件名称: {eventImpl.Name}");
        Console.WriteLine($"  定义在: {eventImpl.ContainingType.ToDisplayString()}");
        Console.WriteLine($"  访问修饰符: {eventImpl.DeclaredAccessibility}");
        
        // 检查是否是显式实现
        if (eventImpl.ExplicitInterfaceImplementations.Length > 0)
        {
            Console.WriteLine("  这是显式接口实现");
        }
        else
        {
            Console.WriteLine("  这是隐式接口实现");
        }
    }
    else
    {
        Console.WriteLine("未找到实现");
    }
}

检查事件的显式接口实现

csharp
/// <summary>
/// 检查事件是否是显式接口实现
/// </summary>
public void CheckExplicitInterfaceImplementation(IEventSymbol eventSymbol)
{
    var explicitImplementations = eventSymbol.ExplicitInterfaceImplementations;
    
    if (explicitImplementations.Length == 0)
    {
        Console.WriteLine("这不是显式接口实现");
        return;
    }
    
    Console.WriteLine($"事件 {eventSymbol.Name} 显式实现了以下接口事件:");
    
    foreach (var interfaceEvent in explicitImplementations)
    {
        Console.WriteLine($"  - {interfaceEvent.ToDisplayString()}");
        Console.WriteLine($"    接口: {interfaceEvent.ContainingType.ToDisplayString()}");
    }
}
点击查看完整示例:事件继承链分析
csharp
/// <summary>
/// 事件继承链分析器
/// </summary>
public class EventInheritanceAnalyzer
{
    public void AnalyzeEventInheritance(IEventSymbol eventSymbol)
    {
        Console.WriteLine($"分析事件: {eventSymbol.ToDisplayString()}");
        Console.WriteLine();
        
        // 1. 检查事件修饰符
        Console.WriteLine("事件修饰符:");
        if (eventSymbol.IsVirtual && !eventSymbol.IsOverride)
            Console.WriteLine("  - virtual (虚事件)");
        if (eventSymbol.IsAbstract)
            Console.WriteLine("  - abstract (抽象事件)");
        if (eventSymbol.IsOverride)
            Console.WriteLine("  - override (重写事件)");
        if (eventSymbol.IsSealed)
            Console.WriteLine("  - sealed (密封事件)");
        Console.WriteLine();
        
        // 2. 构建重写链
        if (eventSymbol.IsOverride || eventSymbol.IsVirtual)
        {
            Console.WriteLine("事件重写链:");
            var chain = BuildOverrideChain(eventSymbol);
            
            for (int i = 0; i < chain.Count; i++)
            {
                var evt = chain[i];
                var indent = new string(' ', i * 2);
                var modifier = GetEventModifier(evt);
                
                Console.WriteLine($"{indent}{modifier} {evt.ToDisplayString()}");
                Console.WriteLine($"{indent}  定义在: {evt.ContainingType.ToDisplayString()}");
            }
            Console.WriteLine();
        }
        
        // 3. 检查接口实现
        if (eventSymbol.ExplicitInterfaceImplementations.Length > 0)
        {
            Console.WriteLine("显式接口实现:");
            foreach (var interfaceEvent in eventSymbol.ExplicitInterfaceImplementations)
            {
                Console.WriteLine($"  - {interfaceEvent.ToDisplayString()}");
            }
        }
    }
    
    private List<IEventSymbol> BuildOverrideChain(IEventSymbol eventSymbol)
    {
        var chain = new List<IEventSymbol>();
        var current = eventSymbol;
        
        // 向上追溯到原始虚事件
        while (current != null)
        {
            chain.Insert(0, current);
            current = current.OverriddenEvent;
        }
        
        return chain;
    }
    
    private string GetEventModifier(IEventSymbol eventSymbol)
    {
        if (eventSymbol.IsAbstract)
            return "abstract";
        if (eventSymbol.IsVirtual && !eventSymbol.IsOverride)
            return "virtual";
        if (eventSymbol.IsOverride && eventSymbol.IsSealed)
            return "sealed override";
        if (eventSymbol.IsOverride)
            return "override";
        
        return "";
    }
}

关键要点:

  • 使用 IsVirtual 检查是否是虚事件
  • 使用 IsOverride 检查是否是重写事件
  • 使用 OverriddenEvent 获取被重写的事件
  • 使用 ExplicitInterfaceImplementations 获取显式实现的接口事件
  • 可以通过循环追溯到原始虚事件

注意

事件重写时,事件类型必须与被重写事件的类型完全相同。


🔗 相关 API


📚 下一步

基于 MIT 许可发布