.NET中那些所謂的新語法之二:匿名類、匿名方法與擴展方法

開篇:在上一篇中,咱們瞭解了自動屬性、隱式類型、自動初始化器等所謂的新語法,這一篇咱們繼續征程,看看匿名類、匿名方法以及經常使用的擴展方法。雖然,都是很常見的東西,可是未必咱們都明白其中蘊含的奧妙。因此,跟着本篇的步伐,繼續來圍觀。html

/* 新語法索引 */

5.匿名類 & 匿名方法
6.擴展方法

1、匿名類:[ C# 3.0/.NET 3.x 新增特性 ]

1.1 很差意思,我匿了

   在開發中,咱們有時會像下面的代碼同樣聲明一個匿名類:能夠看出,在匿名類的語法中並無爲其命名,而是直接的一個new { }就完事了。從外部看來,咱們根本沒法知道這個類是幹神馬的,也不知道它有何做用。函數

    var annoyCla1 = new
    {
        ID = 10010,
        Name = "EdisonChou",
        Age = 25
    };

    Console.WriteLine("ID:{0}-Name:{1}-Age:{2}", annoyCla1.ID,annoyCla1.Name, annoyCla1.Age);

  通過調試運行,咱們發現匿名類徹底能夠實現具名類的效果:工具

1.2 深刻匿名類背後

   既然咱們發現匿名類能夠徹底實現具名類的效果,那麼咱們能夠大膽猜想編譯器確定在內部幫咱們生成了一個相似具名類的class,因而,咱們仍是藉助反編譯工具對其進行探索。經過Reflector反編譯,咱們找到了編譯器生成的匿名類以下圖所示:學習

  從上圖能夠看出:優化

  (1)匿名類被編譯後會生成一個[泛型類],能夠看到上圖中的<>f__AnonymousType0<<ID>j__TPar, <Name>j__TPar, <Age>j__TPar>就是一個泛型類;ui

  (2)匿名類所生成的屬性都是只讀的,能夠看出與其對應的字段也是只讀的;this

  

  因此,若是咱們在程序中爲屬性賦值,那麼會出現錯誤;spa

  

  (3)能夠看出,匿名類還重寫了基類的三個方法:Equals,GetHashCode和ToString;咱們能夠看看它爲咱們所生成的ToString方法是怎麼來實現的:3d

  

  實現的效果以下圖所示:指針

1.3 匿名類的共享

  能夠想象一下,若是咱們的代碼中定義了不少匿名類,那麼是否是編譯器會爲每個匿名類都生成一個泛型類呢?答案是否認的,編譯器考慮得很遠,避免了重複地生成類型。換句話說,定義了多個匿名類的話若是符合必定條件則能夠共享一個泛型類。下面,咱們就來看看有哪幾種狀況:

  (1)若是定義的匿名類與以前定義過的如出一轍:屬性類型和順序都一致,那麼默認共享前一個泛型類

            var annoyCla1 = new
            {
                ID = 10010,
                Name = "EdisonChou",
                Age = 25
            };

            Console.WriteLine("ID:{0}-Name:{1}-Age:{2}", annoyCla1.ID,
                annoyCla1.Name, annoyCla1.Age);
            Console.WriteLine(annoyCla1.ToString());

            // 02.屬性類型和順序與annoyCla1一致,那麼共同使用一個匿名類
            var annoyCla2 = new
                {
                    ID = 10086,
                    Name = "WncudChou",
                    Age = 25
                };
            Console.WriteLine("ID:{0}-Name:{1}-Age:{2}", annoyCla1.ID,
                annoyCla1.Name, annoyCla1.Age);
            Console.WriteLine("Is The Same Class of 1 and 2:{0}",
                annoyCla1.GetType() == annoyCla2.GetType());    

  經過上述代碼中的最後兩行:咱們能夠判斷其是不是一個類型?答案是:True

  (2)若是屬性名稱和順序一致,但屬性類型不一樣,那麼仍是共同使用一個泛型類,只是泛型參數改變了而已,因此在運行時會生成不一樣的類:

            var annoyCla3 = new
                {
                    ID = "EdisonChou",
                    Name = 10010,
                    Age = 25
                };
            Console.WriteLine("ID:{0}-Name:{1}-Age:{2}", annoyCla3.ID,
                annoyCla3.Name, annoyCla3.Age);
            Console.WriteLine("Is The Same Class of 2 and 3:{0}",
                annoyCla3.GetType() == annoyCla2.GetType());

  咱們剛剛說到雖然共享了同一個泛型類,只是泛型參數改變了而已,因此在運行時會生成不一樣的類。因此,那麼能夠猜想到最後兩行代碼所顯示的結果應該是False,他們雖然都使用了一個泛型類,可是在運行時生成了兩個不一樣的類。

  (3)若是數據型名稱和類型相同,但順序不一樣,那麼編譯器會從新建立一個匿名類

            var annoyCla4 = new
                {
                    Name = "EdisonChou",
                    ID = 10010,
                    Age = 25
                };
            Console.WriteLine("ID:{0}-Name:{1}-Age:{2}", annoyCla4.ID,
                annoyCla4.Name, annoyCla4.Age);
            Console.WriteLine("Is The Same Class of 2 and 4:{0}",
                annoyCla4.GetType() == annoyCla2.GetType());

  運行判斷結果爲:False

  經過Reflector,能夠發現,編譯器確實從新生成了一個泛型類:

2、匿名方法:[ C# 2.0/.NET 2.0 新增特性 ]

2.1 從委託的聲明提及

  C#中的匿名方法是在C#2.0引入的,它終結了C#2.0以前版本聲明委託的惟一方法是使用命名方法的時代。不過,這裏咱們仍是看一下在沒有匿名方法以前,咱們是如何聲明委託的。

  (1)首先定義一個委託類型:

public delegate void DelegateTest(string testName);

  (2)編寫一個符合委託規定的命名方法:

        public void TestFunc(string name)
        {
            Console.WriteLine("Hello,{0}", name);
        }

  (3)最後聲明一個委託實例:

    DelegateTest dgTest = new DelegateTest(TestFunc);
    dgTest("Edison Chou");

  (4)調試運行能夠獲得如下輸出:

  由上面的步湊能夠看出,咱們要聲明一個委託實例要爲其編寫一個符合規定的命名方法。可是,若是程序中這個方法只被這個委託使用的話,總會感受代碼結構有點浪費。因而,微軟引入了匿名方法,使用匿名方法聲明委託,就會使代碼結構變得簡潔,也會省去實例化的一些開銷。

2.2 引入匿名方法

  (1)首先,咱們來看看上面的例子如何使用匿名方法來實現:

DelegateTest dgTest2 = new DelegateTest(delegate(string name)
{
      Console.WriteLine("Good,{0}", name);
});

從運行結果圖中能夠看出,本來須要傳遞方法名的地方咱們直接傳遞了一個方法,這個方法以delegate(參數){方法體}的格式編寫,在{}裏邊直接寫了方法體內容。因而,咱們不由歡呼雀躍,又能夠簡化一些工做量咯!

  (2)其次,咱們將生成的程序經過Reflector反編譯看看匿名方法是怎麼幫咱們實現命名方法的效果的。

  ①咱們能夠看到,在編譯生成的類中,除了咱們本身定義的方法外,還多了兩個莫名其妙的成員:

  ②通過一一查看,原來編譯器幫咱們生成了一個私有的委託對象以及一個私有的靜態方法。咱們能夠大膽猜想:原來匿名方法不是沒有名字的方法,仍是生成了一個有名字的方法,只不過這個方法的名字被藏匿起來了,並且方法名是編譯器生成的。

  ③通過上面的分析,咱們仍是不甚瞭解,到底匿名方法委託對象在程序中是怎麼體現的?這裏,咱們須要查看Main方法,可是經過C#代碼咱們沒有發現一點能夠幫助咱們理解的。這時,咱們想要刨根究底就有點麻煩了。還好,在高人指點下,咱們知道能夠藉助IL(中間代碼)來分析一下。因而,在Reflector中切換展現語言,將C#改成IL,就會看到另一番天地。

  (3)由上面的分析,咱們能夠作出結論:編譯器對於匿名方法幫咱們作了兩件事,一是生成了一個私有靜態的委託對象和一個私有靜態方法;二是將生成的方法的地址存入了委託,在運行時調用委託對象的Invoke方法執行該委託對象所持有的方法。所以,咱們也能夠看出,匿名方法須要結合委託使用

2.3 匿名方法擴展

  (1)匿名方法語法糖—更加簡化你的代碼

  在開發中,咱們每每會採用語法糖來寫匿名方法,例以下面所示:

        DelegateTest dgTest3 = delegate(string name)
        {
           Console.WriteLine("Goodbye,{0}", name);
        };
        dgTest3("Edison Chou");

  能夠看出,使用該語法糖,將new DelegateTest()也去掉了。可見,編譯器讓咱們愈來愈輕鬆了。

  (2)傳參也有大學問—向方法中傳入匿名方法做爲參數

  ①在開發中,咱們每每聲明瞭一個方法,其參數是一個委託對象,能夠接受任何符合委託定義的方法。

    static void InvokeMethod(DelegateTest dg)
    {
         dg("Edison Chou");
    }

  ②咱們能夠將已經定義的方法地址做爲參數傳入InvokeMethod方法,例如:InvokeMethod(TestFunc); 固然,咱們也可使用匿名方法,不須要單獨定義就能夠調用InvokeMethod方法。

    InvokeMethod(delegate(string name)
    {
          Console.WriteLine("Fuck,{0}", name);
    });

  (3)省略省略再省略—省略"大括號"

  通過編譯器的不斷優化,咱們發現連delegate後邊的()均可以省略了,咱們能夠看看下面一段代碼:

    InvokeMethod(delegate { 
         Console.WriteLine("I love C sharp!"); 
    });

  而咱們以前的定義是這樣的:

        public delegate void DelegateTest(string testName);

        static void InvokeMethod(DelegateTest dg)
        {
            dg("Edison Chou");
        }

  咱們發現定義時方法是須要傳遞一個string類型的參數的,可是咱們省略了deletegate後面的括號以後就沒有參數了,那麼結果又是什麼呢?通過調試,發現結果輸出的是:I love C sharp!

  這時,咱們就有點百思不得其解了!明明都沒有定義參數,爲什麼仍是知足了符合委託定義的參數條件呢?因而,咱們帶着問題仍是藉助Reflector去一探究竟。

  ①在Main函數中,能夠看到編譯器爲咱們自動加上了符合DelegateTest這個委託定義的方法參數,即一個string類型的字符串。雖然,輸出的是I love C sharp,但它確實是符合方法定義的,由於它會接受一個string類型的參數,儘管在方法體中沒有使用到這個參數。

  ②剛剛在Main函數中看到了匿名方法,如今能夠看看編譯器爲咱們所生成的命名方法。

3、擴展方法:[ C# 3.0/.NET 3.x 新增特性 ]

3.1 神奇—初玩擴展方法

  (1)提到擴展方法,我想大部分的園友都不陌生了。不過仍是來看看MSDN的定義:

MSDN 說:擴展方法使您可以向現有類型「添加」方法,而無需建立新的派生類型、從新編譯或以其餘方式修改原始類型。這裏的「添加」之因此使用引號,是由於並無真正地向指定類型添加方法。

  那麼,有時候咱們會問:爲何要有擴展方法呢?這裏,咱們能夠顧名思義地想一下,擴展擴展,那麼確定是涉及到可擴展性。在抽象工廠模式中,咱們能夠經過新增一個工廠類,而不須要更改源代碼就能夠切換到新的工廠。這裏也是如此,在不修改源碼的狀況下,爲某個類增長新的方法,也就實現了類的擴展。

  (2)空說無憑,咱們來看看在C#中是怎麼來判斷擴展方法的:經過智能提示,咱們發現有一些方法帶了一個指向下方的箭頭,查看「舒適提示」,咱們知道他是一個擴展方法。所得是乃,原來咱們一直對集合進行篩選的Where()方法竟然是擴展方法而不是原生的。

  咱們再來看看使用Where這個擴展方法的代碼示例:

        static void UseExtensionMethod()
        {
            List<Person> personList = new List<Person>()
            {
                new Person(){ID=1,Name="Big Yellow",Age=10},
                new Person(){ID=2,Name="Little White",Age=15},
                new Person(){ID=3,Name="Middle Blue",Age=7}
            };

            // 下面就使用了IEnumerable的擴展方法:Where
            var datas = personList.Where(delegate(Person p)
            {
                return p.Age >= 10;
            });

            foreach (var data in datas)
            {
                Console.WriteLine("{0}-{1}-{2}", 
                    data.ID, data.Name, data.Age);
            }
        }

  上述代碼使用了Where擴展方法,找出集合中Age>=10的數據造成新的數據集並輸出:

  (3)既然擴展方法是爲了對類進行擴展,那麼咱們可不能夠進行自定義擴展呢?答案是必須能夠。咱們先來看看擴展方法是如何的定義的,能夠經過剛剛的IEnumerable接口中的Where方法定義來看看有哪些規則:經過 轉到定義 的方式,咱們能夠看到在System.Linq命名空間下,有叫作Enumerable的這樣一個靜態類,它的成員方法全是靜態方法,並且每一個方法的大部分第一參數都是以this開頭。因而,咱們能夠總結出,擴展方法的三個要素是:靜態類靜態方法以及this關鍵字

    public static class Enumerable
    {
        public static IEnumerable<TSource> Union<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer);
    }

  那麼問題又來了:爲什麼必定得是static靜態的呢?這個咱們都知道靜態方法是不屬於某個類的實例的,也就是說咱們不須要實例化這個類,就能夠訪問這個靜態方法。因此,你懂的啦。

  (4)看完擴展方法三要素,咱們就來自動動手寫一個擴展方法:

    public static class PersonExtension
    {
        public static string FormatOutput(this Person p)
        {
            return string.Format("ID:{0},Name:{1},Age:{2}",
                p.ID, p.Name, p.Age);
        }
    }

  上面這個擴展方法完成了一個格式化輸出Person對象屬性信息的字符串構造,能夠完成上面例子中的輸出效果。因而,咱們能夠將上面的代碼改成如下的方式進行輸出:

        static void UseMyExtensionMethod()
        {
            List<Person> personList = new List<Person>()
            {
                new Person(){ID=1,Name="Big Yellow",Age=10},
                new Person(){ID=2,Name="Little White",Age=15},
                new Person(){ID=3,Name="Middle Blue",Age=7}
            };

            var datas = personList.Where(delegate(Person p)
            {
                return p.Age >= 10;
            });

            foreach (var data in datas)
            {
                Console.WriteLine(data.FormatOutput());
            }
        }

3.2 嗦嘎—探祕擴展方法

  剛剛咱們體驗了擴展方法的神奇之處,如今咱們本着刨根究底的學習態度,藉助Reflector看看編譯器到底幫咱們作了什麼工做?

  (1)經過反編譯剛剛那個UseMyExtensionMethod方法,咱們發現並無什麼奇怪之處。

  (2)這時,咱們能夠將C#切換到IL代碼看看,或許會有另外一番收穫?因而,果斷切換以後,發現了真諦!

  原來編譯器在編譯時自動將Person.FormatOutput更改成了PersonExtension.FormatOutput,這時咱們彷彿茅塞頓開,所謂的擴展方法,原來就是靜態方法的調用而已,所德是乃(原來如此)!因而,咱們能夠將這樣認爲:person.FormatOutput() 等同於調用 PersonExtension.FormatOutput(person);

  (3)再查看所編譯生成的方法,發現this關鍵已經消失了。咱們不由一聲感嘆,原來this只是一個標記而已,標記它是擴展的是哪個類型,在方法體中能夠對這個類型的實例進行操做。

3.3 注意—總結擴展方法

  (1)如何定義擴展方法:

  定義靜態類,並添加public的靜態方法,第一個參數 表明 擴展方法的擴展類。

  a) 它必須放在一個非嵌套、非泛型的靜態類中(的靜態方法);

  b) 它至少有一個參數;

  c) 第一個參數必須附加 this 關鍵字;

  d) 第一個參數不能有任何其餘修飾符(out/ref)

  e) 第一個參數不能是指針類型

  (2)當咱們把擴展方法定義到其它程序集中時,必定要注意調用擴展方法的環境中須要包含擴展方法所在的命名空間

  (3)若是要擴展的類中原本就有和擴展方法的名稱同樣的方法,到底會調用成員方法仍是擴展方法呢?

答案:編譯器默認認爲一個表達式是要使用一個實例方法,但若是沒有找到,就會檢查導入的命名空間和當前命名空間裏全部的擴展方法,並匹配到適合的方法。

參考文章

  (1)一線碼農,《來看看兩種好玩的方法:擴展方法和分部方法》:http://www.cnblogs.com/huangxincheng/p/4021192.html

  (2)Anders Cui,《擴展方法淺談》:http://www.cnblogs.com/anderslly/archive/2010/01/18/using-extension-methods.html

附件下載

  NewGrammerDemos v1.1 : http://pan.baidu.com/s/1pJsOrvd

 

相關文章
相關標籤/搜索