[AaronYang]C#人爱学不学8[事件和.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爽!!

======安徽六安=========www.ayjs.net==========aaronyang========杨洋========www.8mi.me==========

时间: 2024-10-28 10:02:54

[AaronYang]C#人爱学不学8[事件和.net4.5的弱事件深入浅出]的相关文章

[AaronYang]C#人爱学不学[7]

做一个决定,并不难,难的是付诸行动,并且坚持到底 --Aaronyang的博客(www.ayjs.net)-www.8mi.me 1. 委托-我的总结 1.1 委托:面试我都会说,把方法当参数.委托包含的只是一个或多个方法的地址. 示例1:(一次执行多个同方法签名的方法) /* *2015年1月3日23:12:13 aaronyang *网址:www.ayjs.net www.8mi.me */ using System; using System.Collections.Generic; us

[AaronYang]C#人爱学不学[3]

本文章不适合入门,只适合有一定基础的人看.我更相信知识细节见高低,我是从4.0开始学的,终于有时间系统的学习C#5.0,是5.0中的知识,会特殊标记下.但写的内容也可能含有其他版本framework的知识,也是为了方便自己更好的记忆C#知识.--Aaronyang的博客(www.ayjs.net) 1. 继承-的疑虑 1.1 一个类,只有一个父类,可以实现多个接口,还要注意顺序 一个结构,没有父类,可以实现多个接口 接口可以继承多个接口,子类必须全部实现 public class myclass

[AaronYang]C#人爱学不学[6]

不要回头,不要将就,做到这两点,人生就会简单很多幸福很多 --Aaronyang的博客(www.ayjs.net)-www.8mi.me 1. 运算符,还有哪些你能学到? 1.1 不安全运算符: sizeof(.net framework1.0和1.1中的).*.-> .& 1.2 checked和unchecked运算符:溢出异常控制运算符 byte类型只能0-255的数,超过就会导致溢出.执行溢出检查用checked,禁止溢出检查用unchecked. 1.3 类型信息运算符:sizeo

[AaronYang]C#人爱学不学[4]

本文章不适合入门,只适合有一定基础的人看.我更相信知识细节见高低,我是从4.0开始学的,终于有时间系统的学习C#5.0,是5.0中的知识,会特殊标记下.但写的内容也可能含有其他版本framework的知识,也是为了方便自己更好的记忆C#知识.文章内容都是自己总结,无抄袭,如果你觉得文章档次太低,请大牛绕道 --Aaronyang的博客(www.ayjs.net) 1. 泛型-是C#的泛型 1.1 性能方面比非泛型好点,比如拆箱装箱的问题.个人感觉代码可读性更好吧.还有就是 写代码可能可以写出很精

[AaronYang]C#人爱学不学[5]

这世上有三样东西是别人抢不走的:一是吃进胃里的食物,二是藏在心中的梦想,三是读进大脑的书 --Aaronyang的博客(www.ayjs.net) 1. 数组-的疑惑? 1.1  多维数组       例如int[,]  a=new int[2,4]; 共两行,每组4个.     int[,] a={{1,2,3,4},{6,7,8,9}} 1.2  锯齿数组       例如int[][] a=new int[2][];可以确定一共2行,但每行几个不知道,所以可以任意个.导致每行的长度可能不一

[AaronYang]C#人爱学不学[1]

1. 记事本写C#,脱离vs 新建记事本,名字为 helloworld.cs using System; namespace Hello{ public class HelloWorldSay{ static void Main(){ Console.WriteLine("你好,世界"); Console.ReadLine(); return; } } } 简单的使用csc命令编译: 我的目录在:C:\Program Files (x86)\Microsoft Visual Studi

弱引用和弱事件

默認對象實例化後得到的都是強引用,不過有時候對於一些複雜的對象,出於性能考慮,并不希望進行頻繁的初始化,此時弱引用就可以派上用場. 用法:先用WeakReference包裝複雜對象,到需要該複雜對象的時候,檢查一下弱引用的IsAlive屬性,如果true,就可以通過Target直接得到複雜對象,省去了實例化的過程. 簡單的例子: static void Main(string[] args) { var weakRef = GetWeakRef(); GC.Collect(); if (weak

我是一个有点内向爱思考的人,学IT有一年时间了

我是一个有点内向爱思考的人,学IT有一年时间了.我主要是学php,asp这方面html,js,css,sqlserver,c#,mysql个人数据库 也都学了一些,我的优势是在学习期间做过和同学一起完成的商城网项目:“京东商城“,期间让我认识了网站架构模板和后台的强大也积累了不少做这类项目的经验.我对编程语言理解深刻,有很深的编程思想,自学能力强(属于实力派). 我的能力总结: 1.对微软系统的产品有较长时间的操作经验,3年的C#开发经验,项目包括公司进销存软件,人事管理软件以及公司年终抽奖软件

我这种人都学得了编程,你呢?

我这种人都学得了编程,你呢? 声明,我也是在学习中,如果看得起可以跟我一起学习! 先自我介绍一下吧,给没信心的朋友找找信心! 今天是2014年5月7日,正在上初三,还有一个月的时间就初中毕业了. 很多人都认为自己学历不够,英语不好想学但是又不敢学.很多人都认为我很聪明,学习很棒. 其实不是,我都不好意思说我这次月考,考了全级800多名,全级才850多人.初中三年我大概睡了一年的觉,我这种人都能学了编程你呢?? 如果你真的对编程有兴趣就开始学吧,很多教程里都说兴趣是最好的老师,我感觉这句话一点都没