C#教程

关注公众号 jb51net

关闭
首页 > 软件编程 > C#教程 > C# 委托与 Lambda 表达式

C# 委托与 Lambda 表达式转换机制及弱事件模式下的生命周期详解

作者:陈百川

本文介绍了C#委托和Lambda表达式的工作原理,包括委托的内部结构、Lambda表达式的转换机制以及弱事件模式下的生命周期管理,感兴趣的朋友一起看看吧

1. 委托内部结构

委托类型包含三个重要的非公共字段:

2. Lambda 表达式转换为委托实例

C# 编译器会将 lambda 表达式转换成相应的委托实例,具体转换方式依赖于 lambda 是否捕获外部数据。

2.1 不捕获任何外部数据

2.2 捕获实例成员(通过 this 访问)

2.3 捕获非实例成员(例如局部变量)

3. 委托实例的订阅与生命周期

3.1 常规委托/事件订阅

3.2 弱事件订阅

上面用工具重新排版了下,下面是我编辑的原文:

委托类型包含三个重要的非公共字段:_target字段,当委托实例包装一个静态方法时,该字段为空;包装实例方法时,这个字段引用回调方法要操作的对象。_methodPtr字段标识要回调的方法。_invocationList字段引用委托数组。

C#编译器将lambda方法替换为对应的委托实例。

当lambda不获取任何外部数据时,调用只创建一次委托实例并缓存:C#编译器将lambda表达式生成为私有的静态函数(编译器自动取名的方法),并生成一个委托类型的静态字段。当调用使用lambda的方法时,先判断自动生成的静态字段是否为空,不为空则直接返回静态字段引用的委托实例,为空则先创建一个包装静态函数的委托实例赋值给静态委托字段。(这导致被静态字段引用的委托实例不会被释放,但委托实例只会被创建一次)。

当lambda获取实例成员时(通过this指针访问),每次调用都创建新的委托实例:C#编译器将lambda表达式生成为私有的实例函数(编译器自动取名的方法)。每次调用使用lambda的方法时都实时创建一个委托实例包装该自动生成的实例函数。

当lambda获取非实例成员时(不通过当前实例的this指针访问,比如局部变量),C#编译器创建一个私有的辅助类,辅助类拥有对应的公开字段引用非实例成员,在辅助类中将将lambda表达式生成为公开的实例函数。每次调用使用lambda的方法时都生成辅助类实例,引用相同的非实例成员,然后创建委托实例传入辅助类实例。(循环中的闭包陷阱就在于循环中虽然创建了多个辅助类实例与委托实例,但不同辅助类实例引用的非实例成员是同一块内存。lambda 表达式是在循环中创建,但其执行往往是在循环结束后才发生,所以所有回调看到的循环变量都是最终状态。并且不同版本C#实现在循环中可能并没有创建循环次数的辅助类实例,而是在进入方法时只创建一次。我猜测创建了循环次数的委托实例,不然作为字典的键时就应该出错了。但CLR Via C#第四版给的示例代码中委托实例只创建了一次,这可能有点问题,有兴趣的朋友可以分析一下。

lambda被转换为委托实例后,当将该委托实例订阅到常规委托、事件时,事件源对委托实例进行强引用。

当将该委托实例订阅到弱事件时,存在有意思的现象:委托实例的生命周期最起码大于_target引用的对象的生命周期。这是通过ConditionalWeakTable<TKey, TValue>实现的,通过将_target引用的对象设置为key、将委托实例设置为value。该类负责数据间的关联,它对key是弱引用,但保证只要key在内存中,value就一定在内存中。

委托实例通过WeakEventManager<TEventSource, TEventArgs>订阅弱事件时,WeakEventManager<TEventSource, TEventArgs>内部会通过Delegate.Target拿到委托实例中_target引用的对象,作为ConditionalWeakTable的key,委托实例作为ConditionalWeakTable的value进行关联。这样就保证了弱事件模式下委托实例的生命周期至少大于_target引用的对象的生命周期。

public void AddHandler(Delegate handler)
{
    Invariant.Assert(_users == 0, "Cannot modify a ListenerList that is in use");
    object obj = handler.Target;
    if (obj == null)
    {
        obj = StaticSource;
    }
    _list.Add(new Listener(obj, handler));
    AddHandlerToCWT(obj, handler);
}
private void AddHandlerToCWT(object target, Delegate handler)
{
    if (!_cwt.TryGetValue(target, out var value))
    {
        _cwt.Add(target, handler);
        return;
    }
    List<Delegate> list = value as List<Delegate>;
    if (list == null)
    {
        Delegate item = value as Delegate;
        list = new List<Delegate>();
        list.Add(item);
        _cwt.Remove(target);
        _cwt.Add(target, list);
    }
    list.Add(handler);
}

到此这篇关于C# 委托与 Lambda 表达式转换机制及弱事件模式下的生命周期分析的文章就介绍到这了,更多相关C# 委托与 Lambda 表达式内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文