天天看點

[C#基礎知識]專題十三:全面解析對象集合初始化器、匿名類型和隐式類型

引言

   經過前面專題的介紹,大家應該對C# 1和C# 2中的特性有了進一步的了解了吧,現在終于迎來我們期待已久的C# 3中特性,C# 中Lambda表達式和Linq的提出相當于徹底改變我們之前的編碼風格了,剛開始接觸它們,一些初學者肯定會覺得很難了解,但是我相信,隻要多多研究下并且弄明白之後你肯定會愛上C# 3中的所有特性的,因為我自己就是這麼過來的,在去年的這個時候,我看到Lambda表達式和Linq的時候覺得很難了解,而且覺得很奇怪的(因為之前都是用C# 3之前的特性去寫代碼的,雖然C# 3中的特性已經出來很久了,但是自己卻寫的很少,也沒有怎麼去研究,是以就覺得很奇怪,有一種感覺就是——怎麼還可以這樣寫的嗎?),經過這段時間對C# 語言系統的學習之後,才發現新的特性都是建立在以前特性的基礎上的,隻是現在編譯器去幫助我們解析C# 3中提出的特性,是以對于編譯器而言,用C# 3.0中的特性編寫的代碼和C# 2.0中編寫的代碼是一樣的。從這個專題開始,将會為大家介紹C# 3 中的特性,本專題就介紹下C# 3中提出來的一些基礎特性,這些特性也是Lambda表達式和Linq的基礎。

 一、自動實作的屬性

當我們在類中定義的屬性不需要一些額外的驗證時,此時我們可以使用自動實作的屬性使屬性的定義更加簡潔,對于C# 3中自動實作的屬性,編譯器編譯時會建立一個私有的匿名的字段,該字段隻能通過屬性的get和set通路器進行通路。下面就看一個C#3中自動實作的屬性的例子:

/// <summary> 

    /// 自定義類 

    /// </summary> 

    public class Person 

    { 

        // C# 3之前我們定義屬性時,一般會像下面這樣去定義 

        // 首先會先定義私有字段,再定義屬性來對字段進行通路 

        //private string _name; 

        //public string Name 

        //{ 

        //    get { return _name; } 

        //    set { _name = value; } 

        //} 

        // C# 3之後有自動實作的屬性之後 

        // 對于不需要額外驗證的屬性,就可以用自動實作的屬性對屬性的定義進行簡化 

        // 不再需要額外定義一個私有字段了, 

        // 不定義私有字段并不是此時沒有了私有字段,隻是編譯器幫我們生成一個匿名的私有字段,不需要我們在代碼中寫出 

        // 減少我們書寫的代碼 

        // 下面就是用自動實作的屬性來定義的一個屬性,其效果等效于上面屬性的定義,不過比之前更加簡潔了 

        /// <summary> 

        ///  姓名 

        /// </summary> 

        public string Name { get; set; } 

        /// 年齡 

        public int Age { get; private set; } 

        ///  自定義構造函數 

        /// <param name="name"></param> 

        public Person(string name) 

        { 

            Name = name; 

        } 

    } 

 有些人會問——你怎麼知道編譯器會幫我們生成一個匿名的私有字段的呢?對于這點當然通過反射工具來檢視經過編譯器編譯之後的代碼了,下面是用Reflector工具檢視的一張截圖:

如果在結構體中使用自動屬性時,則所有構造函數都需要顯式地調用無參構造函數this(),否則,就會出現編譯時錯誤,因為隻有顯式調用無參構造函數this(),編譯器才知道所有字段都被指派了。下面是一段測試代碼:

    ///  在結構體使用自動屬性 

    public struct TestPerson 

        // 自動屬性 

        // 在結構中所有構造函數都需要顯示地調用無參數構造函數this(), 

        // 否則會出現編譯錯誤 

        // 隻有調用了無參數構造函數,編譯器才知道所有字段都被指派了 

        public TestPerson(string name) 

            //: this() 

            this.Name = name; 

把this()注釋掉後就會出現編譯時錯誤,如下圖:

 二、隐式類型

用關鍵字var定義的變量則該變量就是為隐式類型,var 關鍵字告訴編譯器根據變量的值類推斷變量的類型。是以對于編譯器而言,隐式類型同樣也是顯式的,同樣具有一個顯式的類型。

2.1 隐式類型的局部變量

用var 關鍵字來聲明局部變量,下面一段示範代碼:

static void Main(string[] args) 

        {  

            // 用var聲明局部變量 

        var stringvariable = "learning hard"; 

            stringvariable = 2; 

         } 

 為什麼說用var定義的變量對于編譯器來說還是具有顯式類型呢?在Visual studio中,将滑鼠放在var部分的時候就可以看到編譯器為變量推斷的類型。并且變量仍然是靜态類型,隻是我們在代碼中沒有寫出類型的名稱而已,這個工作交給編譯器根據變量的值去推斷出變量的類型,為了證明變量時靜态類型,當我們把2賦給變量stringvariable時就會出現編譯時錯誤,然而在其他動态語言中,這樣的指派是可以編譯通過,是以用var聲明的變量仍然還是靜态類型,隻是我們在代碼中沒有寫出來而已。下面是證明上面兩點的截圖:

 然而使用隐式類型時有一些限制,具體限制有:

被聲明的變量是一個局部變量,不能為字段(包括靜态字段和執行個體字段)

變量在聲明時必須被初始化(因為編譯器要根據變量的指派來推斷變量的類型,如果沒有被初始化則編譯器就無法推斷出變量類型了, 然而C#是靜态語言則必須在定義變量時指定變量的類型,是以此時變量不知道什麼類型,就會出現編譯時錯誤)

變量的初始化不能初始化為一個方法組,也不能為一個匿名函數(前提是不進行強制類型轉化的匿名函數)

變量不能初始化為null(因為null可以隐式轉化為任何引用類型或可空類型,是以編譯器不能推斷出該變量到底應該為什麼類型)

不能用一個正在聲明的變量來初始化隐式類型 (如不能這樣來聲明隐式類型

)

不能用var來聲明方法中的參數類型

 同時使用隐式類型有優點也有缺點,下面的一段示例代碼完全诠釋了:

// 隐式類型的優點 

            // 對于複雜類型,減少打字量 

            // 使用隐式類型,此時就不需要再指派的左右兩側都指定Dictionary<string,string> 

            var dictionary = new Dictionary<string, string>(); 

            // 在foreach中使用隐式類型 

            foreach (var item in dictionary) 

            { 

                //  

            } 

            // 隐式類型的缺點 

            // 下面代碼使用隐式類型就會使得開發人員很難知道變量的具體類型 

            // 是以對于什麼情況下使用隐式類型,完全取決個人情況,自己感覺是否使用了隐式類型會使代碼看起來更整潔和容易了解 

            var a = 2147483649; 

            var b = 928888888888; 

            var c = 2147483644; 

            Console.WriteLine( "變量a的類型為:{0}",a.GetType()); 

            Console.WriteLine("變量b的類型為:{0}", b.GetType()); 

            Console.WriteLine("變量c的類型為:{0}", c.GetType()); 

            Console.Read(); 

 2.2 隐式類型的數組 

var不僅可以建立隐式類型的局部變量,還可以建立數組,下面是一段示範代碼:

// 隐式類型數組示範 

// 編譯器推斷為int[]類型 

var intarray = new[] { 1,2,3,4}; 

   // 編譯器推斷為string[] 類型 

   var stringarray = new[] { "hello", "learning hard" }; 

   // 隐式類型數組出錯的情況 

   var errorarray = new[] { "hello", 3 }; 

 使用隐式類型的數組時,編譯器必須推斷出使用什麼類型的數組,編譯器首先會構造一個包含大括号裡面的所有表達式(如上面代碼中的 1,2,3,4和"hello","learning hard")的編譯時類型的集合,在這個集合中如果所有類型都能隐式轉換為衛衣的一種類型,則該類型就成為數組的類型,否則,就會出現編譯時錯誤,如代碼中隐式類型數組出錯的情況, 因為"hello"轉化為string,而3卻轉化為int,此時編譯器就不能确定數組的類型到底為什麼,是以就會出現編譯錯誤,錯誤資訊為:"找不到隐式類型數組的最佳類型"

 三、對象集合初始化

3.1  對象初始化

 有了對象初始化特性之後,我們就不需要考慮定義參數不同的構造函數來應付不同情況的初始化了,就減少了在我們實體類中定義的構造函數代碼,這樣使代碼更加簡潔,下面就具體看下C# 3中的對象初始化的使用和注意事項:

namespace 對象集合初始化器Demo 

    class Program 

        static void Main(string[] args) 

            #region 對象初始化示範 

            // 在C# 3.0之前,我們可能會使用下面方式來初始化對象 

            Person person1 = new Person(); 

            person1.Name = "learning hard"; 

            person1.Age = 25; 

            Person person2 = new Person("learning hard"); 

            person2.Age = 25; 

            // 如果類沒有無參的構造函數就會出現編譯時錯誤 

            // 因為下面的語句是調用無參構造函數來對類中的字段進行初始化的 

            // 大括号部分就是對象初始化程式 

            Person person3 = new Person { Name = "learning hard", Age = 25 }; 

            // 下面代碼和上面代碼是等價的,隻不過上面省略了構造函數的圓括号而已 

            Person person4 = new Person() { Name = "learning hard", Age = 25 }; 

            Person person5 = new Person("learning hard") { Age = 25 }; 

            #endregion  

    /// <summary> 

        public int Age { get; set; } 

        ///  定義無參的構造函數 

        ///  如果類中自定義了帶參數的構造函數,則編譯不會生成預設的構造函數 

        ///  如果沒有預設的構造函數,則使用對象初始化時就會報錯說沒有實作無參的構造函數 

        public Person() 

上面代碼中我用紅色标注出使用對象初始化時需要注意的地方,大家也可以通過反射工具檢視編譯器是如何去解析對象初始化代碼的。

 3.2  集合初始化

C# 3中還提出了集合初始化特性來對集合初始化進行了優化,下面是一段集合初始化的使用示範代碼:

            #region 集合初始化示範 

            // C# 3.0之前初始化集合使用的代碼 

            List<string> names = new List<string>(); 

            names.Add("learning hard1"); 

            names.Add("learning hard2"); 

            names.Add("learning hard3"); 

            // 有了C# 3.0中集合初始化特性之後,就可以簡化代碼 

            // 同時下面也使用了隐式類型(使用了var關鍵字) 

            var newnames = new List<string> 

                "learning hard1","learning hard2", "learning hard3" 

            }; 

集合初始化同樣是編譯器自動幫我們調用List的無參構造函數,然後調用Add()方法一個一個地添加進去,對于編譯器而言,C# 3中使用集合初始化的代碼和C#3之前寫的代碼是一樣.然而對于開發人員來說,有了C#3的集合初始化之後,這個過程就不需要我們自己去編碼,而是交給編譯器幫我們做就好了, 為了證明編譯器幫我們所做得事情,下面看看用反射工具來檢視編譯器到底是怎樣幫我們來翻譯集合初始化的:

List<string> names = new List<string>(); 

names.Add("learning hard1"); 

names.Add("learning hard2"); 

names.Add("learning hard3"); 

List<string> <>g__initLocal3 = new List<string>(); 

<>g__initLocal3.Add("learning hard1"); 

<>g__initLocal3.Add("learning hard2"); 

<>g__initLocal3.Add("learning hard3"); 

List<string> newnames = <>g__initLocal3; 

 從上面反射出來的代碼可以看出,編譯器确實是一位大好人,幫我們做了那麼多的事情。

可能大家會有這樣的疑問——對象集合初始化隻不過是一個文法糖而已,就是簡單地讓我們少寫點代碼而已啊,也沒有其他什麼用啊?下面部分的介紹将會解決你們的疑問。

四、匿名類型

 看到匿名類型可能大家會聯想到前面介紹的匿名方法,編譯器對匿名類型和匿名方法都采用同樣的處理方式,該方式為編譯器為匿名類型生成類型名,我們在代碼中不需要顯式自定義一個類型,下面就看看匿名類型的使用:

namespace 匿名類型Demo 

            #region 匿名類型的使用Demo 

            // 定義匿名類型 

            // 因為這裡不知道初始化的類型是什麼,是以這裡就必須使用隐式類型 

            // 此時隐式類型就發揮出了功不可沒的作用,進而說明隐式類型的提出是為了服務于匿名類型的 

            // 而匿名類型的提出又是服務于Linq,一步步都是在微軟團隊的計劃當中 

            Console.WriteLine("進入匿名類型使用示範:"); 

            var person1 = new { Name = "learning hard", Age = 25 }; 

            Console.WriteLine("{0} 年齡為: {1}", person1.Name, person1.Age); 

            Console.WriteLine("按下Enter鍵進入匿名類型數組示範:"); 

            Console.WriteLine(); 

            #endregion 

            #region 匿名類型數組示範 

            // 定義匿名類型數組 

            var personcollection = new[]  

                new {Name ="Tom",Age=30}, 

                new {Name ="Lily", Age=22}, 

                new {Name ="Jerry",Age =32}, 

                // 如果加入下面一句就會出現編譯時錯誤 

                // 因為此時編譯器就不能推斷出要轉換為什麼類型 

                // new {Name ="learning hard"} 

            int totalAge = 0; 

            foreach (var person in personcollection) 

                // 下面代碼證明Age屬性是強類型的int類型 

                totalAge += person.Age; 

            Console.WriteLine("所有人的年齡總和為: {0}", totalAge); 

            Console.ReadKey(); 

運作結果:

 上面匿名類型的示範中使用了前面幾部分介紹的所有特性——隐式類型,對象集合初始化,是以對于前面說對象集合初始化也沒有其他方面的用處的疑問也可以得到答案了,如果沒有對象集合初始化,要寫出這樣的代碼(指的是 var person1 = new { Name = "learning hard", Age = 25 };)還可能嗎?是以前面的隐式類型和對象集合初始化另外的一個用處就是服務于匿名類型的, 然而匿名類型又是服務于Linq的,對于Linq的好處當時是多的數不勝數了, 後面專題中會為大家介紹Linq。

上面還指出雖然我們在代碼中沒有為匿名類型指定類型名,而編譯器會為我們生成一個類型,為了證明這點我們同樣反射工具Reflector檢視下編譯器最後為我們生成的代碼到底是怎樣的?截圖如下:

五、總結

 到這裡,本專題的介紹也就結束了, 本專題就介紹了C# 3中幾個基礎的特性——自動實作的屬性、隐式類型、對象集合初始化和匿名類型,這些類型的提出都是服務于後面更複雜的特性Linq的,是以隻有掌握好這些基礎特性之後,才能更好更快地掌握好Linq。在後面一個專題将和大家聊下C#3中的Lambda表達式。

<a href="http://down.51cto.com/data/2361934" target="_blank">附件:http://down.51cto.com/data/2361934</a>

     本文轉自LearningHard 51CTO部落格,原文連結:http://blog.51cto.com/learninghard/1085243,如需轉載請自行聯系原作者

繼續閱讀