理解隱式類型、對象初始化程序和匿名類型
在C# 3.0中,幾乎每個新特性都是為LINQ服務的。所以,本文將介紹下面幾個在C# 3.0中引入的新特性:
自動實現的屬性
隱式類型的局部變量
對象和集合初始化程序
隱式類型的數組
匿名類型
其實這幾個特性都是比較容易理解的,對於這幾個特性,編譯器幫我們做了更多的事情(想想匿名方法和迭代器塊),從而簡化我們的代碼。
自動實現的屬性
在C# 3.0以前,當我們定義屬性的時候,一般使用下面的代碼
public class Book
{
private int _id;
private string _title;
public int Id
{
get { return _id; }
set { _id = value; }
}
public string Title
{
get { return _title; }
set { _title = value; }
}
}
在C# 3.0中引入了自動實現的屬性,編譯器會幫我們做更多的轉換,所以我們可以把上面的屬性實現代碼轉換為:
public class Book
{
public int Id { get; set; }
public string Title { get; set; }
}
在使用了自動實現的屬性之後,代碼變短了,我們也沒有必要再定義私有的字段。
其實,當查看過IL代碼之後就會發現這裡編譯器幫我們定義了私有字段,實現了get/set方法。
注意,當使用結構的時候,如果要使用自動屬性,會有一個小問題:所有的構造函數都需要顯式地調用無參數的構造函數this(),只有這樣,編譯器才知道所有的字段都被明確的賦值了。
例如下面代碼中,當我們刪除":this()"後,編譯器就會報錯。
public struct Student
{
public int Id { get; set; }
public string Name { get; set; }
public string Gender { get; set; }
//在結構中使用自動屬性一定要顯式地調用無參數的構造函數this()
public Student(string name):this()
{
this.Name = name;
}
}
隱式類型的局部變量
C# 1.0和C# 2.0中的類型系統是靜態、顯示和安全的。
在C# 3.0中我們可以使用var關鍵字定義隱式類型的變量,但是變量仍然是靜態類型,只是編譯器可以幫助我們推斷變量的類型。
下面看一段代碼,使用隱式類型的語句跟注釋掉的語句的IL代碼是相同的:
static void Main(string[] args)
{
var str = "hello world";//string str = "hello world";
var num = 9;// int num = 9;
Console.WriteLine(str.GetType());
Console.WriteLine(num.GetType());
Console.Read();
}
通過代碼的輸出可以看到,每個隱式類型的變量都是靜態類型(同樣我們也可以通過VS單步調試來查看隱式類型變量的類型),在這裡是編譯器幫我們做了類型推斷。
為了驗證這一點,但我們給str變量賦一個整型的值時,就會得到一個"Cannot implicitly convert type 'int' to 'string'"的錯誤。
static void Main(string[] args)
{
var str = "hello world";//string str = "hello world";
str = 9;
Console.Read();
}
隱式類型的限制
使用隱式類型的時候,會有一些限制,不是所有變量都能使用隱式類型:
被聲明的變量是一個局部變量,不能是靜態字段和實例字段
變量在聲明時必須被初始化
編譯器需要根據變量的值來推斷變量的類型,否則就會出現編譯時錯誤
初始化表達式不能為一個方法組,也不能為一個匿名函數(不進行強制類型轉化)
var enter = delegate { Console.WriteLine(); };//編譯錯誤
var enter = (Action)delegate { Console.WriteLine(); };//正常,因為編譯器可以進行類型推斷
初始化表達式不是null
因為null可以隱式轉化為任何引用類型或可空類型,所以編譯器不能進行類型推斷
語句中只能聲明一個變量
"var a = 2, b = 3;"會得到編譯錯誤
隱式類型的優缺點
有些時候使用隱式類型可以減少代碼長度,通過不影響代碼可讀性,反而使我們把注意力放在了更有用的代碼上;但是,有時候隱式類型會是代碼可讀性變差。所以要自己衡量什麼時候使用隱式類型的變量。下面看一個簡單的例子
static void Main(string[] args)
{
//簡化了代碼,沒有犧牲可讀性
var wordCount = new Dictionary<string, int>();
foreach (var dict in wordCount)
{
Console.WriteLine("number of {0} is {1}", dict.Key, dict.Value);
}
//可讀性變差,不容易從代碼中直接看出變量類型
var numA = 2147483647;
var numB = 2147483648;
var numC = 4294967295;
Console.WriteLine(numA.GetType());
Console.WriteLine(numB.GetType());
Console.WriteLine(numC.GetType());
Console.Read();
}
隱式類型的局部變量對象和集合初始化程序
在C# 3.0中,我們有了新的對象和集合初始化的方法。
對象初始化程序(object initializers)
當我們有了對象初始化程序的時候,對象初始化的代碼就變得更加直觀、簡單。看一個例子:
public class Book
{
public int Id { get; set; }
public string Title { get; set; }
//如果沒有默認的構造函數,使用對象初始化時就會報錯
public Book()
{
}
public Book(string title)
{
this.Title = title;
}
}
class Program
{
static void Main(string[] args)
{
//C# 3.0之前初始化對象的方法
Book b1 = new Book();
b1.Id = 1;
b1.Title = "C# step by step";
//使用對象初始化程序
Book b2 = new Book(){Id = 2, Title = "C# in depth"};
Book b3 = new Book { Id = 3, Title = "C# in depth" };
Book b4 = new Book("C# in depth") { Id = 1};
}
}
當我們查看IL代碼會發現,b1、b2和b3的IL代碼完全一樣。
集合初始化列表(collection initializers)
在C# 3.0中還提出來集合初始化列表,我們可以輕松的實現集合的初始化工作。
接著上面的例子,我們可以創建圖書列表:
//C# 3.0之前初始化集合的方法
List<Book> bookList1 = new List<Book>();
bookList1.Add(b1);
bookList1.Add(b2);
bookList1.Add(b3);
bookList1.Add(b4);
//使用集合初始化程序
List<Book> bookList2 = new List<Book> { b1, b2, b3, b4 };
List<Book> bookList3 = new List<Book>
{
new Book{Id = 5, Title = "Java in depth"},
new Book{Id = 6, Title = "Python in depth"},
};
可以看到,當使用了集合初始化列表之後,代碼變得更加簡潔了。
通過查看IL代碼我們可以發現,使用集合初始化列表的時候,其實編譯器幫我們調用了List的Add方法進行了元素的添加。
隱式類型的數組
在C# 1.0和C# 2.0中,當我們使用數組的時候,必須要指定涉及的具體數組類型。
在C# 3.0中,可以使用"new[]"來聲明並初始化一個隱式類型的數組。
看一個簡單的例子:
string[] names = { "Wilber", "Will", "July" };
PrintName(names);
PrintName(new string[] { "Wilber", "Will" });
//C# 3.0中使用匿名類型數組,編譯器負責推斷數組類型
PrintName(new[] { "Wilber", "Will" });
var nameArray = new[] { "Wilber", "Will", "July" };
PrintName(nameArray);
//無法進行類型推斷,編譯器報錯
//var array = new[] { 1, "hello world" };
……
private static void PrintName(string []names)
{
foreach (var name in names)
{
Console.WriteLine(name);
}
}
匿名類型
在C# 3.0之前,當我們創建對象的時候,我們需要一個類型。在C# 3.0中出現了匿名類型的概念(類似於匿名方法,編譯器幫我們完成了很多工作),我們可以直接通過new關鍵字為對象定義了屬性,並且為這些屬性賦值。
看一個例子:
static void Main(string[] args)
{
//創建匿名對象
//通過匿名對象初始化程序為屬性賦值
var student = new { Name = "Wilber", Age = 28, Gender = "Male" };
Console.WriteLine("{0} is {1} years old", student.Name, student.Age);
//使用隱式類型的數組初始化列表
var school = new[] {
//使用同一個匿名類型創建實例
new {Name = "Wilber", Gender = "Male", Age = 28 },
new {Name = "Will", Gender = "Male", Age = 27 },
new {Name = "Lily", Gender = "Female", Age = 26 },
};
int totalAge = 0;
foreach (var stu in school)
{
totalAge += stu.Age;
}
Console.WriteLine(totalAge);
Console.Read();
}