# [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; }
/// <summary>
/// 年齡
/// </summary>
public int Age { get; private set; }
/// <summary>
/// 自定義構造函數
/// </summary>
/// <param name="name"></param>
public Person(string name)
{
Name = name;
}
}
```
有些人會問——你怎么知道編譯器會幫我們生成一個匿名的私有字段的呢?對于這點當然通過反射工具來查看經過編譯器編譯之后的代碼了,下面是用Reflector工具查看的一張截圖:

如果在結構體中使用自動屬性時,則所有構造函數都需要顯式地調用無參構造函數this(),否則,就會出現編譯時錯誤,因為只有顯式調用無參構造函數this(),編譯器才知道所有字段都被賦值了。下面是一段測試代碼:
```
/// <summary>
/// 在結構體使用自動屬性
/// </summary>
public struct TestPerson
{
// 自動屬性
public string Name { get; set; }
// 在結構中所有構造函數都需要顯示地調用無參數構造函數this(),
// 否則會出現編譯錯誤
// 只有調用了無參數構造函數,編譯器才知道所有字段都被賦值了
public TestPerson(string name)
//: this()
{
this.Name = name;
}
}
```
把this()注釋掉后就會出現編譯時錯誤,如下圖:

**二、隱式類型**
用關鍵字var定義的變量則該變量就是為隱式類型,var 關鍵字告訴編譯器根據變量的值類推斷變量的類型。所以對于編譯器而言,隱式類型同樣也是顯式的,同樣具有一個顯式的類型。
**2.1 隱式類型的局部變量**
用var 關鍵字來聲明局部變量,下面一段演示代碼:
為什么說用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>
/// 自定義類
/// </summary>
public class Person
{
/// <summary>
/// 姓名
/// </summary>
public string Name { get; set; }
/// <summary>
/// 年齡
/// </summary>
public int Age { get; set; }
/// <summary>
/// 定義無參的構造函數
/// 如果類中自定義了帶參數的構造函數,則編譯不會生成默認的構造函數
/// 如果沒有默認的構造函數,則使用對象初始化時就會報錯說沒有實現無參的構造函數
/// </summary>
public Person()
{
}
/// <summary>
/// 自定義構造函數
/// </summary>
/// <param name="name"></param>
public Person(string name)
{
Name = name;
}
}
}
```
上面代碼中我用紅色標注出使用對象初始化時需要注意的地方,大家也可以通過反射工具查看編譯器是如何去解析對象初始化代碼的。
**3.2 集合初始化**
C# 3中還提出了集合初始化特性來對集合初始化進行了優化,下面是一段集合初始化的使用演示代碼:
```
namespace 對象集合初始化器Demo
{
class Program
{
static void Main(string[] args)
{
#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"
};
#endregion
}
}
/// <summary>
/// 自定義類
/// </summary>
public class Person
{
/// <summary>
/// 姓名
/// </summary>
public string Name { get; set; }
/// <summary>
/// 年齡
/// </summary>
public int Age { get; set; }
/// <summary>
/// 定義無參的構造函數
/// 如果類中自定義了帶參數的構造函數,則編譯不會生成默認的構造函數
/// 如果沒有默認的構造函數,則使用對象初始化時就會報錯說沒有實現無參的構造函數
/// </summary>
public Person()
{
}
/// <summary>
/// 自定義構造函數
/// </summary>
/// <param name="name"></param>
public Person(string name)
{
Name = name;
}
}
}
```
集合初始化同樣是編譯器自動幫我們調用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
{
class Program
{
static void Main(string[] args)
{
#region 匿名類型的使用Demo
// 定義匿名類型
// 因為這里不知道初始化的類型是什么,所以這里就必須使用隱式類型
// 此時隱式類型就發揮出了功不可沒的作用,從而說明隱式類型的提出是為了服務于匿名類型的
// 而匿名類型的提出又是服務于Linq,一步步都是在微軟團隊的計劃當中
Console.WriteLine("進入匿名類型使用演示:");
var person1 = new { Name = "learning hard", Age = 25 };
Console.WriteLine("{0} 年齡為: {1}", person1.Name, person1.Age);
Console.Read();
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();
#endregion
}
}
}
```
運行結果:

上面匿名類型的演示中使用了前面幾部分介紹的所有特性——隱式類型,對象集合初始化,所以對于前面說對象集合初始化也沒有其他方面的用處的疑問也可以得到答案了,如果沒有對象集合初始化,要寫出這樣的代碼(指的是 var person1 = new { Name = "learning hard", Age = 25 };)還可能嗎?所以前面的隱式類型和對象集合初始化另外的一個用處就是服務于匿名類型的, 然而匿名類型又是服務于Linq的,對于Linq的好處當時是多的數不勝數了, 后面專題中會為大家介紹Linq。
上面還指出雖然我們在代碼中沒有為匿名類型指定類型名,而編譯器會為我們生成一個類型,為了證明這點我們同樣反射工具Reflector查看下編譯器最后為我們生成的代碼到底是怎樣的?截圖如下:

從上面截圖中可以看出編譯器確實為我們生成了一個匿名類型)**[<>f__AnonymousType0](http://www.aisto.com/roeder/dotnet/Default.aspx?Target=code://匿名類型Demo:1.0.0.0/<>f__AnonymousType0<,>)**<**<Name>j__TPar**, **<Age>j__TPar**>(其中代碼相當于我們上面中定義的Person類),編譯器為我們生成的這個類型是直接繼承自System.Object的,并且是internal sealed(指的是該類型只在程序集內可見,并且不能被繼承)。
**五、總結**
到這里,本專題的介紹也就結束了, 本專題就介紹了C# 3中幾個基礎的特性——自動實現的屬性、隱式類型、對象集合初始化和匿名類型,這些類型的提出都是服務于后面更復雜的特性Linq的,所以只有掌握好這些基礎特性之后,才能更好更快地掌握好Linq。在后面一個專題將和大家聊下C#3中的Lambda表達式。
該專題中的演示源碼:[http://files.cnblogs.com/zhili/%E5%9F%BA%E7%A1%80%E7%89%B9%E6%80%A7Demo.zip](http://files.cnblogs.com/zhili/%E5%9F%BA%E7%A1%80%E7%89%B9%E6%80%A7Demo.zip)
- C# 基礎知識系列
- C# 基礎知識系列 專題一:深入解析委托——C#中為什么要引入委托
- C# 基礎知識系列 專題二:委托的本質論
- C# 基礎知識系列 專題三:如何用委托包裝多個方法——委托鏈
- C# 基礎知識系列 專題四:事件揭秘
- C# 基礎知識系列 專題五:當點擊按鈕時觸發Click事件背后發生的事情
- C# 基礎知識系列 專題六:泛型基礎篇——為什么引入泛型
- C# 基礎知識系列 專題七: 泛型深入理解(一)
- C# 基礎知識系列 專題八: 深入理解泛型(二)
- C# 基礎知識系列 專題九: 深入理解泛型可變性
- C#基礎知識系列 專題十:全面解析可空類型
- C# 基礎知識系列 專題十一:匿名方法解析
- C#基礎知識系列 專題十二:迭代器
- C#基礎知識 專題十三:全面解析對象集合初始化器、匿名類型和隱式類型
- C# 基礎知識系列 專題十四:深入理解Lambda表達式
- C# 基礎知識系列 專題十五:全面解析擴展方法
- C# 基礎知識系列 專題十六:Linq介紹
- C#基礎知識系列 專題十七:深入理解動態類型
- 你必須知道的異步編程 C# 5.0 新特性——Async和Await使異步編程更簡單
- 全面解析C#中參數傳遞
- C#基礎知識系列 全面解析C#中靜態與非靜態
- C# 基礎知識系列 C#中易混淆的知識點
- C#進階系列
- C#進階系列 專題一:深入解析深拷貝和淺拷貝
- C#進階系列 專題二:你知道Dictionary查找速度為什么快嗎?
- C# 開發技巧系列
- C# 開發技巧系列 使用C#操作Word和Excel程序
- C# 開發技巧系列 使用C#操作幻燈片
- C# 開發技巧系列 如何動態設置屏幕分辨率
- C# 開發技巧系列 C#如何實現圖片查看器
- C# 開發技巧 如何防止程序多次運行
- C# 開發技巧 實現屬于自己的截圖工具
- C# 開發技巧 如何使不符合要求的元素等于離它最近的一個元素
- C# 線程處理系列
- C# 線程處理系列 專題一:線程基礎
- C# 線程處理系列 專題二:線程池中的工作者線程
- C# 線程處理系列 專題三:線程池中的I/O線程
- C# 線程處理系列 專題四:線程同步
- C# 線程處理系列 專題五:線程同步——事件構造
- C# 線程處理系列 專題六:線程同步——信號量和互斥體
- C# 多線程處理系列專題七——對多線程的補充
- C#網絡編程系列
- C# 網絡編程系列 專題一:網絡協議簡介
- C# 網絡編程系列 專題二:HTTP協議詳解
- C# 網絡編程系列 專題三:自定義Web服務器
- C# 網絡編程系列 專題四:自定義Web瀏覽器
- C# 網絡編程系列 專題五:TCP編程
- C# 網絡編程系列 專題六:UDP編程
- C# 網絡編程系列 專題七:UDP編程補充——UDP廣播程序的實現
- C# 網絡編程系列 專題八:P2P編程
- C# 網絡編程系列 專題九:實現類似QQ的即時通信程序
- C# 網絡編程系列 專題十:實現簡單的郵件收發器
- C# 網絡編程系列 專題十一:實現一個基于FTP協議的程序——文件上傳下載器
- C# 網絡編程系列 專題十二:實現一個簡單的FTP服務器
- C# 互操作性入門系列
- C# 互操作性入門系列(一):C#中互操作性介紹
- C# 互操作性入門系列(二):使用平臺調用調用Win32 函數
- C# 互操作性入門系列(三):平臺調用中的數據封送處理
- C# 互操作性入門系列(四):在C# 中調用COM組件
- CLR
- 談談: String 和StringBuilder區別和選擇
- 談談:程序集加載和反射
- 利用反射獲得委托和事件以及創建委托實例和添加事件處理程序
- 談談:.Net中的序列化和反序列化
- C#設計模式
- UML類圖符號 各種關系說明以及舉例
- C#設計模式(1)——單例模式
- C#設計模式(2)——簡單工廠模式
- C#設計模式(3)——工廠方法模式
- C#設計模式(4)——抽象工廠模式
- C#設計模式(5)——建造者模式(Builder Pattern)
- C#設計模式(6)——原型模式(Prototype Pattern)
- C#設計模式(7)——適配器模式(Adapter Pattern)
- C#設計模式(8)——橋接模式(Bridge Pattern)
- C#設計模式(9)——裝飾者模式(Decorator Pattern)
- C#設計模式(10)——組合模式(Composite Pattern)
- C#設計模式(11)——外觀模式(Facade Pattern)
- C#設計模式(12)——享元模式(Flyweight Pattern)
- C#設計模式(13)——代理模式(Proxy Pattern)
- C#設計模式(14)——模板方法模式(Template Method)
- C#設計模式(15)——命令模式(Command Pattern)
- C#設計模式(16)——迭代器模式(Iterator Pattern)
- C#設計模式(17)——觀察者模式(Observer Pattern)
- C#設計模式(18)——中介者模式(Mediator Pattern)
- C#設計模式(19)——狀態者模式(State Pattern)
- C#設計模式(20)——策略者模式(Stragety Pattern)
- C#設計模式(21)——責任鏈模式
- C#設計模式(22)——訪問者模式(Vistor Pattern)
- C#設計模式(23)——備忘錄模式(Memento Pattern)
- C#設計模式總結
- WPF快速入門系列
- WPF快速入門系列(1)——WPF布局概覽
- WPF快速入門系列(2)——深入解析依賴屬性
- WPF快速入門系列(3)——深入解析WPF事件機制
- WPF快速入門系列(4)——深入解析WPF綁定
- WPF快速入門系列(5)——深入解析WPF命令
- WPF快速入門系列(6)——WPF資源和樣式
- WPF快速入門系列(7)——深入解析WPF模板
- WPF快速入門系列(8)——MVVM快速入門
- WPF快速入門系列(9)——WPF任務管理工具實現
- ASP.NET 開發
- ASP.NET 開發必備知識點(1):如何讓Asp.net網站運行在自定義的Web服務器上
- ASP.NET 開發必備知識點(2):那些年追過的ASP.NET權限管理
- ASP.NET中實現回調
- 跟我一起學WCF
- 跟我一起學WCF(1)——MSMQ消息隊列
- 跟我一起學WCF(2)——利用.NET Remoting技術開發分布式應用
- 跟我一起學WCF(3)——利用Web Services開發分布式應用
- 跟我一起學WCF(3)——利用Web Services開發分布式應用
- 跟我一起學WCF(4)——第一個WCF程序
- 跟我一起學WCF(5)——深入解析服務契約 上篇
- 跟我一起學WCF(6)——深入解析服務契約 下篇
- 跟我一起學WCF(7)——WCF數據契約與序列化詳解
- 跟我一起學WCF(8)——WCF中Session、實例管理詳解
- 跟我一起學WCF(9)——WCF回調操作的實現
- 跟我一起學WCF(10)——WCF中事務處理
- 跟我一起學WCF(11)——WCF中隊列服務詳解
- 跟我一起學WCF(12)——WCF中Rest服務入門
- 跟我一起學WCF(13)——WCF系列總結
- .NET領域驅動設計實戰系列
- .NET領域驅動設計實戰系列 專題一:前期準備之EF CodeFirst
- .NET領域驅動設計實戰系列 專題二:結合領域驅動設計的面向服務架構來搭建網上書店
- .NET領域驅動設計實戰系列 專題三:前期準備之規約模式(Specification Pattern)
- .NET領域驅動設計實戰系列 專題四:前期準備之工作單元模式(Unit Of Work)
- .NET領域驅動設計實戰系列 專題五:網上書店規約模式、工作單元模式的引入以及購物車的實現
- .NET領域驅動設計實戰系列 專題六:DDD實踐案例:網上書店訂單功能的實現
- .NET領域驅動設計實戰系列 專題七:DDD實踐案例:引入事件驅動與中間件機制來實現后臺管理功能
- .NET領域驅動設計實戰系列 專題八:DDD案例:網上書店分布式消息隊列和分布式緩存的實現
- .NET領域驅動設計實戰系列 專題九:DDD案例:網上書店AOP和站點地圖的實現
- .NET領域驅動設計實戰系列 專題十:DDD擴展內容:全面剖析CQRS模式實現
- .NET領域驅動設計實戰系列 專題十一:.NET 領域驅動設計實戰系列總結