首页 > 学院 > 开发设计 > 正文

胡侃软件开发之C#的委托

2019-11-14 10:42:45
字体:
来源:转载
供稿:网友

3 委托

3.1委托的定义:

委托是一种引用类型.是能够持有方法的类类型,他是寻址方式的C#实现.和其他的普通类不一样的是,委托具有签名,同时只能使用相同签名的函数.委托与C++中的函数指针比较类似.允许你传递一个放到到另一个方法中,使得这个方法中可以直接调用传入的函数或者方法. 在引用非静态成员函数时,delegate不但保存了对此函数入口地址的引用,而且还保存了调用此函数的类实例的引用.与函数指针相比,delegate是面向对象、类型安全、可靠的受控对象.也就是说,runtime能够保证delegate指向一个有效的方法,你无须担心delegate会指向无效地址或者越界地址.委托还能够包括对一组方法的引用.

委托的属性(MSDN的定义):

l  委托类似于 C++ 函数指针,但它们是类型安全的.

l  委托允许将方法作为参数进行传递.

l  委托可用于定义回调方法.

l  委托可以链接在一起;例如,可以对一个事件调用多个方法.

l  方法不必与委托类型完全匹配.

 

如果足够细心那么就可以看出其实委托和接口有一定的相似. 委托和接口都允许类设计器分离类型声明和实现.任何类或结构都能继承和实现给定的接口.可以为任何类上的方法创建委托,前提是该方法符合委托的方法签名.接口引用或委托可由不了解实现该接口或委托方法的类的对象使用.其实委托和接口都是定一个规范.只要按照此规范来实现的东西都可以被接受.那么接口和委托在使用应该怎么去考虑呢:

当出现以下情况出现的时候可以考虑使用委托:

l  当定义事件的时候.

l  当需要构成方法链的时候.

l  当类可能需要该方法的多个实现的时候,委托链就是个例子

l  当在多线程或者异步环境中需要通知其他程序的时候.

l  当封装静态方法的时候

l  当调用方不需要访问实现该方法的对象中的其他属性、方法或接口时.

当出现以下情况的时候可以考虑使用接口:

l  当存在一组可能被调用的相关方法时.

l  当类只需要方法的单个实现时.

l  当使用接口的类想要将该接口强制转换为其他接口或类类型时.

l  当正在实现的方法链接到类的类型或标识时:例如比较方法.IComparable 或泛型版本 IComparable<T> 就是一个使用单一方法接口而不使用委托的很好的示例. IComparable 声明 CompareTo 方法,该方法返回一个整数,指定相同类型的两个对象之间的小于、等于或大于关系. IComparable 可用作排序算法的基础. 虽然将委托比较方法用作排序算法的基础是有效的,但是并不理想. 因为进行比较的能力属于类,而比较算法不会在运行时改变,所以单一方法接口是理想的

 

3.2委托的使用

委托的使用在C#中相当的广泛.例如初始化一个线程的时候,传入的参数ThreadStart类型就是就一个委托.

委托的原型如下: 
public abstract class Delegate : ICloneable, ISerializable
从这个原型可以看出delegate类其实就是一个抽象类.在这里不要看到这个Delegate是一个类就可以去继承,微软是不允许继承的.不过透过源码还是可以看到这个类的内部其实还是保存了一个方法的地址句柄.我们日常定义委托的时候其实不是直接继承的这个类而是继承的MulticastDelegate这个类.其定义的原型如下:
public abstract class MulticastDelegate : Delegate
从这个定义中可以看出这个也是一个抽象类,但是这也是一个特殊类,一样不允许继承.在我们定义委托后编译器会自动识别这两个类然后编译成委托类.
委托还有一种用法,就是异步委托.这个在线程中讲过.这里不再更多的详细的讲了.关于委托的内部实现,有兴趣的朋友可以去仔细看看这个类的内部实现代码,其实这个类的内部实现并没有想象的复杂,只是他调用了一堆外部方法没有公开源码(不知道这部分的内部实现如何).另外如果想了解他对方法的调用可以去看看MothedInfo的内部代码. https://referencesource.microsoft.com/#mscorlib/system/delegate.cs

定义一个委托其实严格来说是定义了一个类.然后在声明一个对应的对象并实例化.

 

 

例子如下:

    classtestDelegate

    {

        delegatevoidtestHandler(int i);

        publicvoid test()

        {

            testHandler ttt =newtestHandler(t2);

            ttt.Invoke(1000);

        }

        void t2(int i)

        {

            Console.WriteLine("delegate call para=" + i);

        }

    }

输出结果:

 

 

3.3Action<T>和Func<T>

Action或者Action<T>这类型的委托是不具有返回值的系统定义的委托.Func<T>是系统定义的具有返回值的委托. 这两个类都有很多的重载版本.Action和Func的出现其实是为了减少开发人员对委托的定义,可以直接使用.定义一个委托其实就是定义了一个新类,虽然代码不多,但还是要写一点点的.

例子如下:

    classtestActionAndFunc

    {

        publicvoid test()

        {

            Action<string> a = x =>

            {

                Console.WriteLine("这里输入的是:" + x);

            };

            Func<string,string> f = x =>

            {

                Console.WriteLine("func的输入是:"+ x);

                return"this is func";

            };

            a("调用Aciton");

            var r= f("call func");

            Console.WriteLine("f Return result:" + r);

        }

    }

结果如下:

上面的代码是直接定义的委托类型,在委托的实现中使用的是lambda表达式的方式来完成的.具体lambda的内容后面将会讲到.这样写其实就是让代码变得更加简介.不过从另一个方面来说如果不熟悉这种写发的人看起来会比较吃力.相当于提高了读代码的门槛.

 

3.4多路广播委托

多路委托其实就是将多个委托合并一起调用.我们上面的例子中调用委托的次数和调用方法的次数是一样的.这样的就是单路委托.委托中还有一个很有用的属性就是可以使用”+”号将多个委托对象链接起来,串成一串,这样在调用的时候也是调用一串.如果你看其中某个委托实现不顺眼可以使用”-”将其去掉. 多路委托包含已分配委托的列表. 在调用多播委托时,它会按顺序调用列表中的委托. 只能合并相同类型的委托.但是列表中的顺序是未知的,因此在使用多路委托的时候需要注意不要让其他的委托依赖于某个委托执行的结果.注意多路委托也是继承自MulticastDelegate类的.

例子如下:

    classtestMulticastDelegate

    {

        publicvoid test()

        {

            Action aciton = a;

            aciton += b;

            aciton += c;

            aciton();

            Console.WriteLine("delete a");

            aciton -= a;

            aciton();

            Console.WriteLine("delete b add a");

            aciton -= b;

            aciton += a;

            aciton();

        }

        void a()

        {

            Console.WriteLine("call a");

        }

        void b()

        {

            Console.WriteLine("call b");

        }

        void c()

        {

            Console.WriteLine("call c");

        }

    }

输出结果如下:

 

 

3.5委托的协变与逆变

协变与逆变其实说简单点就是类型转化.我们知道委托其实是一个类.类是具有继承性的.在面向对象的世界里有一个很著名的原则叫里氏代换,任何可以用父类的地方都可以用子类进行代替.基于这个原则来理解协变就容易了,但是这里有点点区别.协变的定义:让一个带有协变参数的泛型接口(或委托)可以接收类型更加精细化,具体化的泛型接口(或委托)作为参数,可以看成面向对象中多态的一个延伸. 协变允许方法具有与接口的泛型类型参数所定义的返回类型相比派生程度更大的返回类型.

举个例子:

 

 

    classtestFT

    {

        publicvoid test()

        {

            IEnumerable<String> t1 = newList<String>(newstring[] { "123","456" });

            IEnumerable<Object> t2 = t1;

            foreach(var x in t2)

            {

                Console.WriteLine(x);

            }

        }

}

在这段代码中可以看出object是string类型的基类,这种转变方式是复合上面协变的要求和过程.但是这里有一点注意的就是IEnumerable<string>接口不是继承自IEnumerable<object>接口,不过呢,string却是继承自object.因此是符合协变定义,所以IEnumerable<T> 接口为协变接口.

 

那么逆变呢?其实理解了协变在来理解逆变就相对简单多了,反正都是针对具体参数来说明的.逆变定义: 让一个带有协变参数的泛型接口(或委托)可以接收粒度更粗的泛型接口或委托作为参数,这个过程实际上是参数类型更加精细化的过程. 逆变允许方法具有与接口的泛型形参所指定的实参类型相比派生程度更小的实参类型.其实逆变简单来说就是针对带有返回类型的委托的参数来说明的.

下面来个简单的例子:

    classtestNB

    {

        classb1

        { }

        classb1_1 :b1

        { }

        classtbc :IComparable<b1>

        {

            publicint GetHashCode(b1 baseInstance)

            {

                return baseInstance.GetHashCode();

            }

            publicbool Equals(b1 x, b1 y)

            {

                return x == y;

            }

            publicint CompareTo(b1 other)

            {

                returnthis.GetHashCode() ==other.GetHashCode() ? 0 : 1;

            }

        }

        publicvoid test()

        {

            IComparable<b1> bc = newtbc();

            IComparable<b1_1> cc = bc;

        }

}

 

从这个例子中可以看出第一b1_1这个类是继承自b1的,类tbc是继承自ICamparable<b1>的,下面test方法中泛型内的两个b1是基类类型,b1_1是子类类型.但这里的基类的实例bc是指向了子tbc类的一个实例.因此在后面逆变才能够成功.

其实泛型的协变和逆变是针对泛型的参数和泛型接口的返回值来说的,不是针对泛型本身的.

下面这些接口是常用的协变与逆变接口

l  IEnumerable<T>(T 为协变)

l  IEnumerator<T>(T 为协变)

l  IQueryable<T>(T 为协变)

l  IGrouping<TKey, TElement>(TKey 和 TElement 为协变)

l  IComparer<T>(T 为逆变)

l  IEqualityComparer<T>(T 为逆变)

l  IComparable<T>(T 为逆变)

注意:

1 协变与逆变中的所有类型都引用类型,值类型是无法完成协变与逆变的.

2 若要启用隐式转换,必须使用 in 或 out 关键字,将委托中的泛型参数显式声明为协变或逆变.如果仅使用变体支持来匹配方法签名和委托类型,且不使用 in 和 out 关键字,你会发现用相同的 lambda 表达式或方法来实例化多个委托,但无法将一个委托指派给另一个委托.

 

上面说了这么多其实都是说的接口和泛型的逆变与协变.下面来说说委托的协变与逆变.

用于在 C#的所有委托中匹配方法签名和委托类型.意思就是不仅可以为委托指派具有匹配签名的方法,而且可以指派返回与委托类型指定的派生类型相比,派生程度更大的类型(协变),或者接受相比之下,派生程度更小的类型的参数(逆变). 这包括泛型委托和非泛型委托.这个有点拗口,意思就是,在指定委托的时候你可以使用基类类型来代替定义中所使用的子类类型.也可以使用更小的子类类型来代替基类类型.

下面举个例子来说明委托的协变:

 

    classtestDelegateBB

    {

        classparent

        {

            publicvirtualvoid tt()

            {

                Console.WriteLine("parent tt");

            }

        }

        classson :parent

        {

            publicoverridevoid tt()

            {

                Console.WriteLine("son tt");

            }

        }

        delegateparentphandler();

        publicvoid test()

        {

            phandler ph = p1;

            var x = ph.Invoke();

            x.tt();

            ph = p2;

            var xx = ph.Invoke();

            xx.tt();

        }

        parent p1() {returnnewparent(); }

        son p2() {returnnewson(); }

    }

输出结果:

这里就发生协变,委托类型声明的时候不是子类son,但是son继承了父类parent,因此这个操作是可以通过的.

委托的逆变:

    classtestDelegateNB

    {

        classpara { }

        classp1 :para { }

        classp2 :para { }

        delegatevoidph1(p1p);

        delegatevoidph2(p2p);

        publicvoid test()

        {

            ph1 px1 = tt;

            ph2 px2 = tt;

            p1 p_1 =newp1();

            p2 p_2 =newp2();

            px1.Invoke(p_1);

            px2.Invoke(p_2);

        }

        void tt(para p)

        {

            Console.WriteLine("ok");

        }

    }

 

在这个例子中两个委托的签名是不一样的.但是却可以指向同一个方法.这就逆变的作用.逆变在C#自身中有很多地方都有实现.常见的就是窗体界面的很多事件都可以指向同一个实现方法.

比如:

        PRivatevoid MultiHandler(object sender, System.EventArgs e)

        {       }

        this.button1.KeyDown +=this.MultiHandler;

        this.button1.MouseClick +=this.MultiHandler;

这个代码在实现的编程中可以帮你减少很多代码.很常用的方法.但实际上两个事件的签名不完全相同,只不过都有共同的基类.

 

说了这么多其实委托的协变与逆变总结下来很简单的:

协变和逆变会提供用于使委托类型与方法签名匹配的灵活性.协变允许方法具有的派生返回类型比委托中定义的更多. 逆变允许方法具有的派生参数类型比委托类型中的更少.说白了就是协变主要是针对参数来说的,逆变主要是针对返回类型来说的.

 

3.6 Lambda表达式

3.6.1  Lambda表达式的简述

在之前的版本的没有lambda表达式,那么要声明一个委托必须使用一个命名方法,后来出现了匿名方法,不过在后来匿名方法被lambda取代,来作为内联代码的首选项.但是这并不能说明匿名表达式就比lambda差,还有一些功能是lambda表达式很难完成的比如可以使用匿名表达式来忽略参数(给参数指定默认值).也就是说匿名方法可以转化为多种签名的委托.这个是一个很强大的功能,你可以定义一个方法签名的时候给默认值,那么这个方法可以适应很多委托,这将减少很多代码.不过这里不讨论仅仅说说而已.有兴趣的可以去看看关于匿名方法.

Lambda表达式其实也是一种匿名函数.可以用于创建委托或者表达式树目录类型的一种函数. 通过使用 lambda 表达式,可以写入可作为参数传递或作为函数调用值返回的本地函数.lambda表达式的如何定义等可以直接看下面的例子.

下面来一个简单的例子:

    classtestlambda_1

    {

        publicvoid test()

        {

            Func<int,string > a = (x) =>

            {

                Console.WriteLine("this is lambda simple para=" + x);

                return"this is lambda return value ";

            };

            var s = a(100);

            Console.WriteLine(s);

        }

    }

输出结果如:

 

上面的例子中定义了一个返回string的,带有int输入参数的委托,使用”=>”符号表示定义一个lambda表达式,左侧为输入参数,括号中的x就是输入参数,”{}”这个的部分就是函数的执行体.注意”=>”符号是又结合的运算符.上面的这个写法是不是很简单呢.用起来也很方便.不过还是有一些需要注意的地方

l  当只有一个参数的时候”()”这个括号是可以省略的(没有参数或者更多参数的时候都是不能够省略的);例如下面的写法:Action<string> action=x=>{};

l  is或者as运算的左侧不允许出现lambda表达式的.

l  在不容易推算出参数类型的时候可以在表达式树中指定参数类型.例如(int x,stirng xx)=>{};

l  Lambda表达式主题应该是在Framework框架之内,跨越了这个框架其实就无意义了.

l  语句 lambda 也不能用于创建表达式目录树.lambda表达式和语句的区别在于:表达式就是一个简单的语句.没有用大括号括起来,通常表达式内容大多是几个判断语句合在一起.而表达式语句则可以有大括号括起来内容也多很多.其实这两个也没有严格的区别,就是一个写法上少了大括号另一个多了大括号.

3.6.2 异步 Lambda

在Framework4.5之后的版本中可以使用async和await关键字来实现异步lambda.使用起来非常简单.

 

 

例子如下:

    classtestLambda_2

    {

        publicvoid test()

        {

            Action action =null;

            action += async () =>

            {

                Console.WriteLine("start " + DateTime.Now.ToString());

                await test2();

                Console.WriteLine("over " + DateTime.Now.ToString());

            };

            action();

        }

        asyncTask test2()

        {

           await Task.Delay(3000);

        }

    }

这段代码就是创建一个Action的异步表达式.通过使用async和await关键字即可完成.这其实要归功于async和await关键字的功能,这点本身和lambda表达式关系不大.

 

 

3.6.3 Lambda的类型推断

在使用lambda表达式的时候通常可以不用输入具体的参数类型(从上面例子中也可以看出),编译器会根据lambda的主体或者参数的一些其他描述或者上下文来自动推断参数或者委托的参数的类型.比如对于Func<T>这类型的最后一个参数一般为返回类型.一般的推断规则如下:

l  Lambda 包含的参数数量必须与委托类型包含的参数数量相同.

l  Lambda 中的每个输入参数必须都能够隐式转换为其对应的委托参数.

l  Lambda 的返回值(如果有)必须能够隐式转换为委托的返回类型.

注意,lambda 表达式本身没有类型,因为C#的常规类型系统没有“Lambda 表达式”这个概念.我们常说的lambda表达式的类型其实是指委托类型或 lambda 表达式所转换到的 Expression 类型.

 

3.6.4 Lambda的变量作用域

定义在lambda函数方法内,包括定义在签名上的参数和定义在持有lambda表达式的外部方法中的变量和其他全局变量等都可以被lambda表达访问.不过在这里有一个需要特别说明的地方,lambda表达式在引用lambda表达式范围外的变量的时候需要在表达式内进行保存,以保证在外部变量超出了范围并被垃圾回收之后在lambda内部还可以使用.

Lambda表达式的变量范围和限制如下:

l  捕获的变量将不会被作为垃圾回收,直至引用变量的委托符合垃圾回收的条件.

l  在外部方法中看不到 lambda 表达式内引入的变量.

l  Lambda 表达式无法从封闭方法中直接捕获 ref 或 out 参数.

l  Lambda 表达式中的返回语句不会导致封闭方法返回.

l  不能使用goto,break,continue语句来跳出lambda表达式,同样也不能够使用这些语句来直接进入lambda表达式.

 

 

 

例子如下:

    classtestLambda_3

    {

        int count = 0;

        publicvoid test()

        {

            int cc = 101;

            Action<string> action = x =>

            {

                count++;

                Console.WriteLine("lambda count=" + count);

                Console.WriteLine("para x=" + x);

                Console.WriteLine("lambda cc=" + ++cc);

            };

            action("wo cao");

            Console.WriteLine("outer cc=" + ++cc);

            Console.WriteLine(" count=" + ++count);

        }

    }

输出结果如下:

 

 

3.7事件

3.7.1 普通事件

事件其实是一个对委托的进一部分封装,可以让事件的接受者使用更加简单. 类或对象可以通过事件向其他类或对象通知发生的相关事情.发送事件的类称为“发行者”,接收事件或者事件处理者的类称为“订户”.事件具有以下特性:

l  发行者确定何时引发事件;订户确定对事件作出何种响应.

l  一个事件可以有多个订户. 订户可以处理来自多个发行者的多个事件.

l  没有订户的事件永远也不会引发.

l  事件通常用于表示用户操作,例如单击按钮或图形用户界面中的菜单选项.

l  当事件具有多个订户时,引发该事件时会同步调用事件处理程序. 若要异步调用事件

l  在Framework 的类库中,事件是基于 EventHandler 委托和 EventArgs 基类来扩展或者使用的.

下面给一个简单的例子来说明事件:

    classtestEventA

    {

        delegatevoidmyEventHandler();

        classA :IDisposable

        {

            //这是一种简写的定义方式

            publiceventmyEventHandler myEvent;

            /*这是完整的定义方法,建议使用下面这种方式

             * 在很多时候需要对事件进行额外的处理

             * 比如在WPF中的隧道事件和冒泡事件

            */

            myEventHandler _myEvntt2;

            publiceventmyEventHandler myEvntt2

            {

                add

                {

                    _myEvntt2 +=value;

                }

                remove

                {

                    _myEvntt2 -=value;

                }

            }

            AutoResetEvent reset =newAutoResetEvent(false);

            bool running =false;

            public A()

            {

                reset.Reset();

                running = true;

                //这里初始化一个定时触发两个事件的线程

                Task.Factory.StartNew(() =>

                {

                    while (running)

                    {

                        if (myEvent !=null)

                        {

                            myEvent();

                        }

                        if (_myEvntt2 !=null)

                        {

                            _myEvntt2();

                        }

                        reset.WaitOne(3000);

                    }

                    reset.Dispose();

                });

            }

            publicvoid Dispose()

            {

                running = false;

                reset.Set();

            }

        }

        publicvoid test()

        {

            A a =newA();

            Task.Run(() =>

            {

                a.myEvntt2 += a_myEvntt2;

                a.myEvent += a_myEvent;

                a.myEvntt2 += () =>

                {

                    a.myEvntt2 -= a_myEvntt2;

                    Console.WriteLine("this is innnn  asdfasdfasdf");

                };

                Thread.Sleep(10 * 1000);

                a.Dispose();

            });

        }

        void a_myEvent()

        {

            Console.WriteLine("this is myEvent  " + DateTime.Now.ToString());

        }

        void a_myEvntt2()

        {

            Console.WriteLine("this is myEvent2  " + DateTime.Now.ToString());

        }

      }

输出结果:

这段代码有点长,其实功能很简单,主要是一个事件发送者类(A,定义了事件,并且负责发送事件)和一个事件处理代码.这里用到了事件链(其实就多路委托).从上面的代码用可以看到定义事件其实就只需要使用event关键字声明一个委托类型的属性或者字段即可.不过推荐使用属性的方式来声明事件.事件的订阅只需使用”+=”,取消订阅只需要使用”-=”即可.

   不过既然有可委托可以完成同样的工作那么为什么还要有事件呢?其实这个是从代码简写上来说的.这个就是微软为大家考虑的属于一种福利.本质上事件就是一种委托.

   我们来看看事件内部是什么样子的呢,不过这里有点遗憾没有找到对应的源码,不过翻遍出来的到时不错;

刚才的代码事件其实在内部还是编译成了属性,不过上面来个事件的内部实现不一样哦(一个是字段方式定义,一个属性方式定义)

我们先来看第一种方式使用字段来直接定义事件的:

public event myEventHandlermyEvent;

这个代码在编译之后其实不是直接使用的委托来合并的.这个通过反编译的代码可以看出来:

通过上面的代码可以看出来,通过该方法定义的属性需要经过很多步骤,甚至需要一个寻来比较和合并.这其实是比较耗时的.

那么我们来看看通过属性定义的方式编译后的代码会是什么样子呢:

看到这个代码是不是觉得比上面的代码要简单多了,就一行代码.

注意不论是声明为字段还是属性编译器都在内部编译成add_和一个 remove_ 这样的方法.在这些方法内部使用的变量都是一个私有变量.在合并委托的时候都是采用了System.Delegate的Combine()静态方法,这个方法用于将当前的变量添加到委托链表中.

这里特别说明几点:

1如果使用匿名函数订阅事件,事件的取消订阅过程将比较麻烦. 这种情况下若要取消订阅,必须返回到该事件的订阅代码,将该匿名方法存储在委托变量中,然后将此委托添加到该事件中. 一般来说,如果必须在后面的代码中取消订阅某个事件,则建议您不要使用匿名函数订阅此事件

2 如果事件或者委托采用异步委托,当事件或者委托的接收方不唯一的时候必须要先获取事件或者委托的链,然后在逐个发起异步事件,否则程序将抛出ArgumentException{"该委托必须有一个目标(且仅有一个目标)."}的异常.

3 事件默认情况是线程不安全的.

3.7.2 弱事件

强事件中有一个很不好处理的问题,通过订阅发布事件之后直接引用到事件的发布者上面,这回给垃圾回收器回收此事件带来麻烦.从而导致内存泄漏.因为侦听程序不引用的时候,发布程序还会有一个引用.这个时候垃圾收集器就不能够回收该对象.针对这种强连接提出一种弱事件的方式来解决这个问题.在发布程序和侦听程序之间建立一个中介,这个中介来负责管理事件对象的引用.

弱事件出现的目的是为了解决强事件引用在某些情况下导致的内存泄漏的问题而出现的.通常来说侦听程序附加事件处理程序会导致侦听程序的对象的生存期不确定,该生存期受源的对象生存期影响(除非显式移除了事件处理程序). 但在某些情况下,可能希望侦听程序的对象生存期受其他因素(例如对象生存期当前是否属于应用程序的可视化树)控制,而不受源的生存期控制. 如果源对象生存期超出了侦听器的对象生存期,则常规事件模式会导致内存泄漏:侦听程序保持活动状态的时间比预期要长.弱事件模式旨在解决此内存泄漏问题. 每当侦听程序需要注册事件,而该侦听程序并不明确了解什么时候注销事件时,就可以使用弱事件模式. 当源的对象生存期超出侦听程序的有用对象生存期时,也可以使用弱事件模式.使用弱事件模式,侦听器可以注册和接收事件,同时不会对侦听器的对象生存期特征产生任何影响. 实际上,来自源的隐含引用无法确定侦听程序是否适合进行垃圾回收. 该引用为弱引用,仅是弱事件模式和相关 API 的命名.可以对侦听器进行垃圾回收或销毁,在不保留对现在销毁的对象的非可收集处理程序引用的情况下,源可以继续.

下面给一个强事件导致的内存泄漏的例子(这个例子可能无法完全说明事件导致内存泄漏,但至少可以给一个提醒或者演示):

    classtestEventOutMemory

    {

        classEventSender

        {

            publicstaticint Count = 0;

            public EventSender()

            {

                Interlocked.Add(ref Count, 1);

            }

            ~EventSender()

            {

                Interlocked.Decrement(ref Count);

            }

            eventAction _EventA;

            publiceventAction EventA

            {

                add

                {

                    _EventA +=value;

                }

                remove

                {

                    _EventA -=value;

                }

            }

            publicvoid ShowEvent()

            {

                if (_EventA !=null)

                {

                    _EventA();

                }

            }

        }

        classEventAccept

        {

            publicstaticint Count = 0;

            public EventAccept()

            {

                Interlocked.Add(ref Count, 1);

            }

            ~EventAccept()

            {

                Interlocked.Decrement(ref Count);

            }

            privateList<StringBuilder> sb = newList<StringBuilder>();

            publicvoid Do()

            {

                for (var i = 0; i < 1000;i++)

                {

                    sb.Add(newStringBuilder(10240));

                }

            }

        }

        List<EventSender> lst = newList<EventSender>();

        publicvoid test()

        {

            for (int i = 0; i < 100;i++)

            {

                EventSender s =newEventSender();

                s.EventA += newEventAccept().Do;

                s.ShowEvent();

                lst.Add(s);  //这句也注释掉后观察进程锁好用的内存大小;

            }

            GC.Collect();

            GC.WaitForFullGCComplete();

            Console.WriteLine("Sender Count=" + EventSender.Count +",Accept count=" +EventAccept.Count);

        }

    }

结果输出和在任务管理器中看到的内存:

对象数量:

内存耗用:

当注释掉lst.add(s);这句之后的状态:

从这两个的对比可以看出内存泄漏相当严重的.不过呢,其实大家也没有必要过多的担心,虽然强事件引用会导致内存泄漏,但绝大多数的情况下是不会导致内存泄漏的,只需要控制好每个对象的生命周期(不过这个说起来容易,项目做起来后就不容易了).

使用弱事件需要继承WeekEvenManager类(注意此类在WindowsBase库里面的System.Windows命名空间中,此类需要单独引用),这玩意儿其实是在wpf中推出的,所以wpf中的很多事件管理都是弱事件.从WeekEventMananger类的继承层次也可以看出:

下面给出弱事件的使用例子:

 

    classtestWeekEvent

    {

        classEventSend

        {

            publiceventAction<object,EventArgs>OnEvent;

            publicvoid DoEvent()

            {

                if (OnEvent !=null)

                {

                    OnEvent(this,newEventArgs());

                }

            }

        }

        classEventWeekEvent:WeakEventManager

        {

            publicstaticvoid AddListener(object source,IWeakEventListenerlistener)

            {

               CurrentManager.ProtectedAddListener(source, listener);

            }

            publicstaticvoid RemoveListener(object source,IWeakEventListenerlistener)

            {

               CurrentManager.ProtectedRemoveListener(source, listener);

            }

            protectedoverridevoid StartListening(object source)

            {

                var s = (sourceasEventSend);

                if (s !=null)

                {

                    s.OnEvent += s_EventA;

                }

            }

            void s_EventA(object obj, EventArgs e)

            {

                DeliverEvent(obj, e);

            }

            protectedoverridevoid StopListening(object source)

            {

                var s = (sourceasEventSend);

                if (s !=null)

                {

                    s.OnEvent -= s_EventA;

                }

            }

            publicstaticEventWeekEvent CurrentManager

            {

                get

                {

                    EventWeekEvent manger = GetCurrentManager(typeof(EventWeekEvent))asEventWeekEvent;

                    if (manger ==null)

                    {

                        manger =newEventWeekEvent();

                        SetCurrentManager(typeof(EventWeekEvent), manger);

                    }

                    return manger;

                }

            }

        }

        classEventAccept:IWeakEventListener

        {

            privatevoid Execute()

            {

                Console.WriteLine("OK");

            }

            publicbool ReceiveWeakEvent(Type managerType, object sender,EventArgse)

            {

                Execute();

                returntrue;

            }

        }

        publicvoid test()

        {

            EventSend s =newEventSend();

            //使用自定义方式的弱事件

            EventWeekEvent.AddListener(s,newEventAccept());

            //使用泛型方式的弱事件

            WeakEventManager<EventSend,EventArgs>.AddHandler(s,"OnEvent",newEventHandler<EventArgs>((o, e) =>

            {

                Console.WriteLine("fuck OK");

            }));

            s.DoEvent();

        }

    }

在这个例子中使用了两种方式来实现弱事件.第一种采用的是以前Framework3.0中提供的WeekEventManager类来实现.但在4.5之后微软提供了一个WeekEventManager类的泛型版本,因此可以直接使用,因此代码量会减少很多.上面的EventWeekEvent这个类完全是为了实现弱事件锁增加的代码.使用泛型版本的则可以省略这些代码.

特别注意:

弱事件中事件的参数必须符合这个标准:

1 事件源必须有,且是第一个参数,事件源不能为null.

2 第二个参数必须是EventArg的子类或者本身;

如果需要更多的内容需要自己去扩展.这两个规范其实也是Framework中对于事件的一种规范.在Framework中事件是基于任何有效委托都可以成功,但是推荐使用EventHandler或者EventHandler<TEventArgs>的泛型版本.

 

3.8观察者模式

观察者模式其实大家见到这个名字就知道是什么意思了.如果还不明白,举个简单的例子:比如古代的青楼都有一个老鸨在门口看到有人进来就里面去说,哎哟喂,这位管人进来玩玩吧我们这里的姑娘可是吹拉弹唱样样精通哦.然后就通知姑娘出来接客了.这个过程中其实就是一个典型的观察者模式(中国古人很智慧的).老鸨就一个事件发起者,姑娘就是事件接收者.而这个客人就是一个被监视的对象.

观察者模式的定义: Observer设计模式是为了定义对象间的一种一对多的依赖关系,以便于当一个对象的状态改变时,其他依赖于它的对象会被自动告知并更新.Observer模式是一种松耦合的设计模式.

下面给出例子:

 

    classtestObserver

    {

        classlaobao

        {

            eventAction<string> _CustomerCome;

            publiceventAction<string> CustomCome

            {

                add

                {

                    _CustomerCome +=value;

                }

                remove

                {

                    _CustomerCome -=value;

                }

            }

            public laobao()

            {

                Task.Run(() =>

                {

                    Thread.Sleep(1000);

                    Console.WriteLine("门口出现了客人,老鸨发现了");

                    if (_CustomerCome !=null)

                    {

                        _CustomerCome("姑娘们出来接客了...");

                    }

                });

            }

        }

        classmein1

        {

            publicvoid AcceptCustom(string cmd)

            {

                Console.WriteLine("美女 1 收到老鸨的接客通:" + cmd);

                Console.WriteLine("美女 1 从房间里出来接客");

            }

        }

        classmein2

        {

            publicvoid AcceptCustom(string cmd)

            {

                Console.WriteLine("美女 2 收到老鸨的接客通:" + cmd);

                Console.WriteLine("美女 2 从房间里出来接客");

            }

        }

        classmein3

        {

            publicvoid AcceptCustom(string cmd)

            {

                Console.WriteLine("美女 3 收到老鸨的接客通:" + cmd);

                Console.WriteLine("美女 3 从房间里出来接客");

            }

        }

        publicvoid test()

        {

            laobao _m =newlaobao();

            mein1 m1 =newmein1();

            mein2 m2 =newmein2();

            mein3 m3 =newmein3();

            _m.CustomCome += m1.AcceptCustom;

            _m.CustomCome += m2.AcceptCustom;

            _m.CustomCome += m3.AcceptCustom;

        }

    }

 

输出结果:

这个就是一个典型的观察者模式.当然观察者模式应用相当广泛.传统的跨服务器的消息通知其实都是基于这种思想来实现的.

 

文章pdf下载地址

http://download.csdn.net/detail/shibinysy/9742961


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表