.NET中那些所謂的新語法之一:自動屬性、隱式類型、命名參數與自動初始化器

開篇:在平常的.NET開發學習中,咱們每每會接觸到一些較新的語法,它們相對之前的老語法相比,作了不少的改進,簡化了不少繁雜的代碼格式,也大大減小了咱們這些菜鳥碼農的代碼量。可是,在開心歡樂之餘,咱們也不由地對編譯器內部到底爲咱們作了哪些事兒而感到好奇?因而,咱們就藉助反編譯神器,去看看編譯器到底作了啥事!其實本篇中不少都不算新語法,對於不少人來講可能都是接觸了好久了,這裏主要是針對.NET的老版原本說,是一個「相對」的新語法。html

/* 新語法索引 */

1.自動屬性 Auto-Implemented Properties
2.隱式類型 var
3.參數默認值 和 命名參數
4.對象初始化器 與 集合初始化器 { }

1、自動屬性探祕:[ C# 3.0/.Net 3.x 新增特性 ]

1.1 之前的作法:先寫私有變量,再寫公有屬性

    public class Student
    {
        private Int32 _id;

        public Int32 Id
        {
            get { return _id; }
            set { _id = value; }
        }
        private string _name;

        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }
        private Int16 _age;

        public Int16 Age
        {
            get { return _age; }
            set { _age = value; }
        }
    }

1.2 如今的作法:聲明空屬性

    public class Person
    {
        public Int32 ID { get; set; }
        public string Name { get; set; }
        public Int16 Age { get; set; }
    }

PS:如今看來,是否是少些不少代碼?直接聲明一個空屬性,編譯器就能夠幫咱們完成之前的私有成員字段和get、set方法,因而,咱們能夠經過Reflector反編譯工具去看看,究竟是怎麼完成這個操做的。小程序

1.3 偉大的「鄉村基」—CSC(C Sharp Compiler):C#編譯器

PS:這裏爲什麼會提到鄉村基,一是由於鄉村基的簡稱就是CSC,二是由於本人比較喜歡吃鄉村基的中式快餐,因此,麼麼嗒!(感受像是給鄉村基打廣告似的,不過我仍是蠻喜歡鄉村基的,固然是拋開價格來講)函數

  (1)首先咱們來編譯一下上面這個小程序,而後將編譯後的exe/dll拖到反編譯神器Reflector(或者ILSpy也是贊贊噠)工具

  (2)找到Person類,能夠看到編譯後的結果:CSC幫咱們自動生成了與共有屬性對應的私有字段學習

  咱們能夠從圖中看出,自動生成的字段與之前的字段有一些區別:優化

  ①在每一個字段上方都加上了一個[CompilerGenerated]的特性(Attribute),顧名思義:表示其是由編譯器生成的;spa

  ②每一個字段的變量名稱是有必定格式的,好比<Age>k__BackingField,那麼能夠看出格式爲:<屬性名>k_BackingField;(BackingField顧名思義就是背後的字段)3d

  (3)看完了自動生成的字段,再來看看屬性是怎麼定義的:調試

  ①和自動生成的字段同樣,屬性也加上了[CompilerGenerated]的特性以示區別code

  ②衆所周知,屬性就是一個get和一個set的兩個方法的封裝,那麼咱們以前寫的空get/set方法又是怎麼被編譯生成的呢

  因而,咱們能夠看到,在get和set方法中,也加上了[CompilerGenerated]的特性以示區別,另外還幫咱們自動對應了自動生成的私有字段,這就跟咱們本身手動寫的私有字段+共有屬性的方法保持了一致。因此,自動屬性是一個實用的語法糖,幫咱們作了兩件事:自動生成私有字段,自動在get/set方法中匹配私有字段。

2、隱式類型—關鍵字:var [ C# 3.0/.Net 3.x 新增特性 ]

2.1 猶抱琵琶半遮面—你能猜出我是誰?

   之前,咱們在定義每一個變量時都須要明確指出它是哪一個類型。可是,當有了var以後,一切變得那麼和諧,咱們能夠用一個var定義全部的類型。

    var age = 100;
    age += 150;

    var name = "";
    name = "edisonchou";

    Console.WriteLine("age={0}", age);
    Console.WriteLine("name={0}", name);

  點擊調試,發現編譯器自動幫咱們匹配上了正確的類型併成功顯示出來:

  那麼,咱們又好奇地想知道編譯器究竟是否識別出來了指定的類型,因而咱們再次經過反編譯工具來一看究竟:

  能夠看出,咱們可愛的CSC正確地幫咱們推斷出了正確的類型,不禁得想給它點32個讚了!

  可是,變量類型不可更改,由於聲明的時候已經肯定類型了,例如咱們在剛剛的代碼中給變量賦予不一樣於定義時的類型,會出現錯誤。

2.2 好刀用在刀刃上—隱式類型應用場景

  在數據型業務開發中,咱們會對一個數據集合進行LINQ查詢,而這個LINQ查詢的結果多是ObjectQuery<>或IQueryable<>類型的對象。所以,在目標具體類型不明確的狀況下,咱們能夠用var關鍵來聲明:

List<UserInfo> userList = roleService.LoadRoles(param);
var data = from u in userList
           where u.IsDel == 0
           select u;

2.3 但「愛」就是剋制—隱式類型使用限制

  (1)被聲明的變量是一個局部變量,而不是靜態或實例字段;

  (2)變量必須在聲明的同時被初始化,編譯器要根據初始化值推斷類型;

  (3)初始化不是一個匿名函數,同時初始化表達式也不能是 null

  (4)語句中只聲明一次變量,聲明後不能更改類型;(詳見上面的例子)

  (5)賦值的數據類型必須是能夠在編譯時肯定的類型

3、參數默認值和命名參數:[ C# 4.0/.NET 4.0 新增特性 ]

3.1 帶默認值的方法

        static void Main(string[] args)
        {
            // 01.帶默認值參數函數
            FuncWithDefaultPara();
            // 02.省略一個默認參數調用
            FuncWithDefaultPara(10086);

            Console.ReadKey();
        }

        static void FuncWithDefaultPara(int id = 10010, bool gender = true)
        {
            Console.WriteLine("Id:{0},Gender:{1}", id,
                gender ? "Man" : "Woman");
        }

  點擊調試,顯示結果以下:

3.2 編譯後的方法調用

  一樣,爲了一探帶參數默認值方法調用的細節,咱們仍是藉助反編譯神器查看其中的玄妙:

  (1)首先,咱們來看看帶默認值參數的方法被編譯後是怎麼的:

  能夠看到,在.NET Framework中大量採用了基於Attribute的開發方式,這裏爲參數添加了表示默認值的特性DefaultParameterValue

  (2)其次,再來看看Main函數中的調用過程是怎麼被編譯的:

  

  能夠看出,編譯器幫咱們在方法調用的括號中幫咱們填充了默認值。這裏,咱們不由好奇,若是在調用中,不指定ID(即便用ID默認值10010)而僅僅指定Gender爲false是否能夠編譯經過?咱們來試一下:

        static void Main(string[] args)
        {
            // 01.帶默認值參數函數
            FuncWithDefaultPara();
            // 02.省略一個默認參數調用
            FuncWithDefaultPara(10086);
            // 錯誤調用:
            FuncWithDefaultPara(false);

            Console.ReadKey();
        }

  這時,出現瞭如下錯誤:

  因而,咱們知道,CSC也尚未那麼智能,沒法理解咱們高深的「意圖」。那麼,有木有一種方法來解決這種需求呢,因而命名參數橫空出世了。

3.3 使用命名參數

  在新語法中爲方法調用引入了命名參數,格式爲 參數名:參數值

        static void Main(string[] args)
        {
            // 01.帶默認值參數函數
            FuncWithDefaultPara();
            // 02.省略一個默認參數調用
            FuncWithDefaultPara(10086);
            // 錯誤調用:
            //FuncWithDefaultPara(false);
            // 03.使用命名參數調用
            FuncWithDefaultPara(gender: false);

            Console.ReadKey();
        }    

  經過調試,能夠獲得以下結果:

  經過前面的分析,咱們能夠分析出,使用命名參數被編譯以後仍是會生成指定參數值的調用:

4、自動初始化器:[ C# 3.0/.NET 3.x 新增特性 ]

4.1 屬性初始化器

  (1)在開發中,咱們常常會這些爲new出來的對象設置屬性:

        static void InitialPropertyFunc()
        {
            Person p = new Person() { Name = "小強", Age = 18 };
            Console.WriteLine("Name:{0}-Age:{1}", p.Name, p.Age);
        }    

  (2)可是,通過編譯後咱們發現原來仍是之前的方式:先new出來,而後一個屬性一個屬性地賦值。這裏,編譯器首先生成了一個臨時對象g_initLocal0,而後爲其屬性賦值,最後將g_initLocal0這個對象的地址傳給要使用的對象p。

4.2 集合初始化器

  (1)在開發中,咱們常常在一個集合的實例化中,就爲其初始化:

        static void InitialCollectionFunc()
        {
            List<Person> personList = new List<Person>()
            {
                new Person(){Name="小強",Age=10},
                new Person(){Name="小王",Age=15},
                new Person(){Name="小李",Age=18}
            };

            foreach(Person person in personList)
            {
                Console.WriteLine("Name:{0}-Age:{1}", 
                    person.Name, person.Age);
            }
        }

  (2)通過上面的集合初始化器咱們瞭解到編譯器仍是會編譯成原來的方式,即先new出來,爲其分配了內存空間以後,再一個一個地爲其屬性賦值。那麼,在集合的初始化中咱們也能夠大膽地猜想,編譯器也是作了以上的優化工做:即先將每一個對象new出來,而後一個一個地爲屬性賦值,最後調用集合的Add方法將其添加到集合中。因而,咱們仍是反編譯一下,一探究竟:

附件下載

  (1)反編譯神器之Reflector: http://pan.baidu.com/s/1evCJG

  (2)所謂的新語法Demo:http://pan.baidu.com/s/1ntwqdAT

 

相關文章
相關標籤/搜索