• .net4.5的弱事件


    .net4.5的弱事件

    没有伟大的愿望,就没有伟大的天才--Aaronyang的博客(www.ayjs.net)-www.8mi.me

    1. 事件-我的讲法

    老师常告诉我,事件是特殊的委托,为委托提供了一种发布/订阅机制。

    1. 自定义事件:自定义一个类,继承EventArgs
    2. 使用泛型委托EventHandler<T>,本质:public delegate void EventHandler<TEventArgs>(object sender,TEventArgs e) where  TEventHandlers:EventArgs
    3. 定义事件的简单写法    public event EventHandler<TEventArgs> myevent;这里的EventHandler<TEventArgs>指的是符合2中的方法签名形式,void,有两个参数的,第一个sender参数包含事件的发送者。第二个参数提供了事件的相关信息。因为这个事件可以自定义,所以可以带来很多丰富的信息,也可以使用系统自带的。
    4. 使用事件属性中的add和remove添加或者删除委托的处理程序(事件),也可以自己使用+=或者-=处理
    5. 使用事件

    1.1 我们来写一个简单的例子,让你理解这几个知识。

    需求:我需要用户使用我提供的类,可能就是个dll了,可以在自己的类中,定义事件的详细内容。例如:博客事件,有了1篇新文章,可能会有阅读者去阅读博客,有的人使用电脑,有的人使用手机,有的人使用kindle

    设计:

    第一步:自定义事件,好处是可以在触发事件时候提供其他的对象值

    复制代码
        public class BlogEventArgs : EventArgs {
            /// <summary>
            /// 博客标题
            /// </summary>
            /// <param name="title"></param>
            public BlogEventArgs(string title) {
                this.Title = title;
            }
            //博客标题
            public string Title { get; set; }
    
        }
    复制代码

    第二步:定义个事件,方便用户增加 自己的定义的事件,删除自己的定义的事件,一个调用用户绑定好的事件(特殊的委托)的方法

    复制代码
        public class BlogEventProvider
        {
            /// <summary>
            /// 第一步:定义个事件,并封装,给用户绑定这个事件
            /// </summary>
            private event EventHandler<BlogEventArgs> blogEvents;
    
            public event EventHandler<BlogEventArgs> BlogEvents
            {
                add
                {
                    blogEvents += value;
                }
                remove
                {
                    blogEvents -= value;
                }
            }
    
    
            //触发用户绑定的事件,或者事件前加些处理,然后再触发
            public void PreviewReadBlog(string title) {
                Console.WriteLine("正在获取博客《{0}》的内容...",title);
                OnReadBlog(title);
            }
    
            protected virtual void OnReadBlog(string title) {
                if (blogEvents != null) {
                    blogEvents(this, new BlogEventArgs(title));
                }
            }
    
    
        }
    复制代码

    第三步:定义个博客支持的设备枚举

    复制代码
        /// <summary>
        /// 博客文章支持的设备
        /// </summary>
        public enum BlogSupportDevice
        {
            PC,
            Kindle,
            Mobile
        }
    复制代码

    第四步:定义阅读博客的人,当然这里的事件的实现,也可以写在别处。

    复制代码
        /// <summary>
        /// 博客阅读者
        /// </summary>
        public class BlogReader
        {
            public BlogReader(string readername, BlogSupportDevice device) {
                this.ReaderName = readername;
                this.Device = device;
            }
            public string ReaderName { get; set; }
            public BlogSupportDevice Device { get; set; }
    
            /// <summary>
            /// 因为需要知道博客的标题还有其他细节,所以自定义了一个BlogEventArgs类
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            public void OnReadBlog(object sender,BlogEventArgs e) {
                Console.WriteLine("{3}:{0}使用了{1}阅读了《{2}》",ReaderName,Device.ToString(),e.Title,DateTime.Now.ToLocalTime());
            }
    
    
        }
    复制代码

    第五步:使用

    第六步:拓展与反思

     1. 如果我绑定了N次相同的事件,然后执行事件,是只会触发1次,还是触发N次?

        答案:是N次,这也是上篇文章留的题目,委托被绑定多个相同的方法会执行多次吗?

     2. 完整第五步代码,使用-=移除上一个事件,不然执行事件时候,会执行多次,因为事件是特殊的委托,具有多播委托的特性。

    复制代码
            static void Main(string[] args)
            {
                //定义了事件提供者
                BlogEventProvider provider = new BlogEventProvider();
    
                //定义博客阅读人
                BlogReader ay = new BlogReader("小aaronyang", BlogSupportDevice.PC);
                //ay开始绑定自己定义的事件,事件提供者,也在触发这个事件之前加了一些措施,而不是任由用户自己搞的
                provider.BlogEvents += ay.OnReadBlog;
                //开始阅读博客
                provider.PreviewReadBlog("[Aaronyang] 写给自己的WPF4.5 笔记[2依赖属性]");
          
    
    
                provider.BlogEvents -= ay.OnReadBlog;//移除ay阅读者
    
                //定义博客阅读人
                BlogReader yy = new BlogReader("杨洋", BlogSupportDevice.Kindle);
                //ay开始绑定自己定义的事件,事件提供者,也在触发这个事件之前加了一些措施,而不是任由用户自己搞的
                provider.BlogEvents += yy.OnReadBlog;
                //开始阅读
                provider.PreviewReadBlog("[Aaronyang] 写给自己的WPF4.5 笔记[1布局]");
    
                Console.ReadLine();
            }
    复制代码

    效果图:

     2. 事件不复杂,就好比js中把方法当做一个参数传递,例如 function A(B,d){B()}

        这里B是一个方法,人家调用A时候就可以自定义一个B,而B只是一个function对象,具有几个参数,在A函数的参数中未体现,但是在调用B时候就有体现了。假设B函数具有2个参数(x,y),那么

         function A(B,d){ var e=B(1,3)}

         function cusB(x,y){return x*y}

         A(cusB,'自定义')

         说白了,感觉有点像给用户留事件接口

    ===============aaronyang========www.8mi.me==================

     

    2.弱事件-引入

    理解事件的发布和订阅模型-publisher和 listener

    aaronyang大话讲解:publisher就是定义事件的地方,如上例就是BlogEventProvider类,它里面定义了private event EventHandler<BlogEventArgs> blogEvents;

    而Listener就是给这个事件 添加或者删除具体事件实现的地方。如上例就是BlogReader类,它里面有blogEvents符合事件(blogEvents)的方法签名。因此就可以正常用+=和-=委托的东东了。

    OK,下面我们来测试为什么需要弱事件?对的,就是防止事件的内存泄露问题。

    接下来,我们在Program类中加一个垃圾回收方法,方便测试

    复制代码
      static void TriggerGC()
            {
                Console.WriteLine("Starting GC.");
    
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();
    
                Console.WriteLine("GC finished.");
            } 
    复制代码

    第一个 GC.Collect() 触发.net的CLR垃圾收集器,对于负责清理不再使用的对象,和那些类中没有终结器(即c#中的析构函数)的对象,CLR垃圾收集器足够胜任

    GC.WaitForPendingFinalizers() 等待其他对象的终结器执行;

    第二个GC.Collect() 确保新生成的对象也被清理了


    接下来,我们接着上一个例子继续写,上面第五步的代码假设你有了。

    我们现在把ay对象置空,接着让垃圾回收ay对象,然后我们执行事件

    复制代码
          static void Main(string[] args)
            {
                //定义了事件提供者
                BlogEventProvider provider = new BlogEventProvider();
    
                //定义博客阅读人
                BlogReader ay = new BlogReader("小aaronyang", BlogSupportDevice.PC);
                //ay开始绑定自己定义的事件,事件提供者,也在触发这个事件之前加了一些措施,而不是任由用户自己搞的
                provider.BlogEvents += ay.OnReadBlog;
                //开始阅读博客
                provider.PreviewReadBlog("[Aaronyang] 写给自己的WPF4.5 笔记[2依赖属性]");
    
    
    
                //provider.BlogEvents -= ay.OnReadBlog;//移除ay阅读者
    
                ////定义博客阅读人
                //BlogReader yy = new BlogReader("杨洋", BlogSupportDevice.Kindle);
                ////ay开始绑定自己定义的事件,事件提供者,也在触发这个事件之前加了一些措施,而不是任由用户自己搞的
                //provider.BlogEvents += yy.OnReadBlog;
                ////开始阅读
                //provider.PreviewReadBlog("[Aaronyang] 写给自己的WPF4.5 笔记[1布局]");
    
                
                //测试事件的强引用 ay
                ay = null;
                TriggerGC();
                provider.PreviewReadBlog("[Aaronyang] 写给自己的WPF4.5 笔记[3高级事件]");
            
    
                Console.ReadLine();
            }
    复制代码

    结果:

    结果惊讶的发现:事件还能被执行!这就说明ay对象还在啊,这就是强引用,所以好多C#写代码都在执行完了事件后,就接着写其他的代码了,也不管垃圾回收能不能回收,导致了内存泄露的问题。

    接着,我们再在 BlogEventProvider中增加一个析构函数,确认BlogEventProvider对象是否真的被回收

     ~BlogEventProvider(){
                Console.WriteLine("BlogEventProvider的析构函数被执行");
            }

    OK,我们再在Program的Main方法中增加代码:

                provider = null;
                TriggerGC();

    效果:

    OK,我确定此时ay对象被释放了。当然不一定要去释放ay对象才能回收内存啊,事件的 内存泄露问题,你只要+=后,执行完,手动去-=事件就行了。这样,垃圾回收器在后台也就可以自动回收ay对象了,不然,你一直在使用provider对象时候,垃圾回收永远都不能回收ay,假设provider中事件绑定了很多事件实现,而这些实现,你执行了,但是都没有做-=,那么就会导致能存泄露的问题,当然也就是内存高占用的问题。好吧,假如你想再new一个provider,那个废弃,那你new的太多的话,也会很多内存,而你如果没有手动GC,那内存占用更恐怖。

                provider.BlogEvents -= ay.OnReadBlog;
                ay = null;
                TriggerGC();
                provider.PreviewReadBlog("[Aaronyang] 写给自己的WPF4.5 笔记[3高级事件]");

    那么这个问题,怎么解决?.net4.5提供了一个更直接的方法,弱引用,当然4.5以前也有弱引用的其他方式,当然比较复杂,这里不讲了,只讲4.5的方式。

    3.弱事件-aaronyang开讲

    如果用4.5的泛型模式,则写法超简单,再也不用继承WeakEventManager,IWeakEventListener了。MSDN位置:查看

    而且再也不用担心绑定多次同一个事件,被执行多次了

    我们使用

              System.Windows.WeakEventManager<BlogEventProvider, BlogEventArgs>.AddHandler(provider, "BlogEvents", ay.OnReadBlog);

    替代刚才的

    provider.BlogEvents += ay.OnReadBlog;

    前提:你要引入WindowsBase.dll

    复制代码
      static void Main(string[] args)
            {
                //定义了事件提供者
                BlogEventProvider provider = new BlogEventProvider();
    
                //定义博客阅读人
                BlogReader ay = new BlogReader("小aaronyang", BlogSupportDevice.PC);
                //ay开始绑定自己定义的事件,事件提供者,也在触发这个事件之前加了一些措施,而不是任由用户自己搞的
                //provider.BlogEvents += ay.OnReadBlog;
                System.Windows.WeakEventManager<BlogEventProvider, BlogEventArgs>.AddHandler(provider, "BlogEvents", ay.OnReadBlog);
                //测试绑定多次,也只会执行一次
                //System.Windows.WeakEventManager<BlogEventProvider, BlogEventArgs>.AddHandler(provider, "BlogEvents", ay.OnReadBlog);
    
                //开始阅读博客
                provider.PreviewReadBlog("[Aaronyang] 写给自己的WPF4.5 笔记[2依赖属性]");
                ay = null;
                TriggerGC();
                provider.PreviewReadBlog("[Aaronyang] 写给自己的WPF4.5 笔记[3高级事件]");
    
                Console.ReadLine();
            }
    复制代码

    效果:

    发现第二次执行事件时候,ay已经被释放了。真TM爽!!

  • 相关阅读:
    ABAPSAP的SCreen录入的简单模板
    SD--如何编程提取SD的定价信息
    如何从SAP中连接其他数据库
    许多的图书下载连接列表
    2006最新版个人所得税计算器
    如何编写上传(PC>AS)和下载(AS->PC)程序示例
    关于sap的字段和对象修改记录的查找
    添加视频播放器
    oracle中exp,imp的使用详解
    [转载]简历中的自我评价用语──珍藏版
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/4207377.html
Copyright © 2020-2023  润新知