委託

委託

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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章