重新認識 C# [3] - C# 3,為成就 LINQ 而生
2 |
【本系列是我的 C# in Depth 第四版讀書筆記,背景故事在這裡】
讀書記筆晃晃悠悠來到 C# 3 惹。C# 3 的改良重點集中在 LINQ,許多新特性算是配合 LINQ 而生。以今天的角度來看,心得可能會是「靠,這還要講?」,請用溫故知新的心態面對,哈。
筆記附上各特性的原文術語,未來 Google 爬文找資料很好用。
- Automatically Implemented Property
//C# 2
private string name;
public string Name
{
get { return name; }
private set { name = value; }
}
//C# 3
public string Name { get; private set; }
- Implicitly Typed Local Variable
現在每天在用的 var x = ... 宣告,從 C# 3 開始 - Implicity Typed Array,不用宣告陣列元素的型別,讓 Compiler 自己推斷
int[] array1 = { 1, 2, 3, 4, 5 };
int[] array2 = new int[] { 1, 2, 3, 4, 5 };
int[] array3 = new[] { 1, 2, 3, 4, 5 };
var array4 = new[,] { {1, 2, 3}, {4, 5, 6} }
- 承上,Compiler 如何推斷元素型別?
- 先找出一組侯選型別(Candidate Type)
- 針對每個元素試試能否成功隱含轉型,淘汰失敗者
- 篩選完若只剩一個型別吻合,即為推斷元素型別(Inferred Element Type),否則抛出編譯錯誤
new[] { "xyz", null } // string[]
new[] { "abc", new object() } // object[]
new[] { 10, new DateTime() } // 錯誤
new[] { 10, null } // 錯誤
- Object Initializer 與 Collection Initializer
new Customer() { Name = "Jon", Address = "UK" }
new Customer { Name = "Jon", Address = "UK" }
new Order {
Items = // Collection Initializer,已知 Items 為 IEnumerable<Item>,new Item[] 可省
{
new Item { ... },
new Item { ... }
}
}
new HttpClient
{
DefaultRequestHeaders = // 指定巢狀屬性物件的初始值
{
From = "user@example.com",
Date = DateTimeOffset.UtcNow
}
}
// Collection Initializer
new List<string> { "John", "Paul", "Ringo", "George" }
new Dictionary<string, int>
{
{ "Please please me", 1963 },
{ "Revolver", 1966 },
{ "Sgt. Pepper's Lonely Hearts Club Band", 1967 },
{ "Abbey Road", 1970 }
}
- Anonymous Type 匿名型別
不需宣告,直接 new { 屬性 = ... }
var player = new
{
Name = "Rajesh",
Score = 3500
}
- Projection Initializer
new
{
order.OrderId, // 取 OrderId 當屬性名,相當於 OrderId = order.OrderId
CustomerName = customer.Name, //取不同屬性名
customer.Address,
item.ItemId,
item.Quantity
}
- Anonymous Type 背後會產生對映的 Compiler-Generated Type,屬性名稱順序不同將是不同型別
var players = new[]
{
new { Name = "Priti", Score = 6000 },
new { Score = 7000, Name = "Chris" }, // 引發錯誤 CS0826 No best type found for implicitly-typed array
new { Name = "Amanda", Score = 8000 },
}
- Lambda Expression
Anonymous Funtion 包含 Anonymous Method 與 Lambda Expression
// 傳統寫法
Action<string> action = delegate(string message)
{
Console.WriteLine("In delegate: {0}", message);
};
// Lambda
Action<string> action = (string message) =>
{
Console.WriteLine("In delegate: {0}", message);
};
Action<string> action =
(string message) => Console.WriteLine("In delegate: {0}", message);
Action<string> action =
(message) => Console.WriteLine("In delegate: {0}", message);
Action<string> action = //單一參數時,() 可省
message => Console.WriteLine("In delegate: {0}", message);
Func<int, int, int> multiply =
(int x, int y) => { return x * y; };
Func<int, int, int> multiply = (int x, int y) => x * y;
Func<int, int, int> multiply = (x, y) => x * y;
- Capturing Variable
建立 Anonymous Function 時補捉當下的變數內容,就是 Closure 的概念。Compiler 會將 Lambda 轉成方法:
* 完全沒補捉變數 -> 建立靜態方法
* 補捉 Instance Field -> 建立 Instance 方法
* 補捉本地變數 -> 建立私有巢狀型別(例如:LambdaContext),LambdaContext 有 Field 存放捕捉的變數,Lambda 表示式轉為其 Instance 方法
若在 foreach 中呼叫用到本地變數的 Lambda Expression,每次會建一個新的 LambdaContext 以保存當次的本地變數
(書中有詳解,此處略) - Expression Tree
將程式碼以資料形式呈現。Delegate 提供你可以跑的程式、Expression Tree 提供你可以檢視內容的程式。
// Lambda 轉 Expression Tree
Expression<Func<int, int, int>> adder = (x, y) => x + y;
Console.WriteLine(adder); //得到 (x, y) => x + y
// 手工做出 ExpressionTree
ParameterExpression xParameter = Expression.Parameter(typeof(int), "x");
ParameterExpression yParameter = Expression.Parameter(typeof(int), "y");
Expression body = Expression.Add(xParameter, yParameter);
ParameterExpression[] parameters = new[] { xParameter, yParameter };
Expression<Func<int, int, int>> adder =
Expression.Lambda<Func<int, int, int>>(body, parameters);
Console.WriteLine(adder); ////得到 (x, y) => x + y
// 不是所有 Lambda 都能轉 Expression Tree
// CS0834 A lambda expression with a statement body cannot be converted to an expression tree
Expression<Func<int, int, int>> adder = (x, y) => { return x + y; }; //錯誤
// Object Initializer 與 Collection Initializer 的存在讓 Lambda 得以順利轉成 Expression Tree
// Expression Tree .Compile() 可執行
Expression<Func<int, int, int>> adder = (x, y) => x + y;
Func<int, int, int> executableAdder = adder.Compile();
Console.WriteLine(executableAdder(2, 3));
- Extention Method 擴充方法
LINQ 之所以讓你在任何 IEnumerable<T> 使用 Where()、Select() 全靠擴充方法
namespace NodaTime.Extensions
{
public static class DateTimeOffsetExtensions //必須是 static class
{
// 第一個參數加 this,指定此方法要擴充在什麼型別上
public static Instant ToInstant(this DateTimeOffset dateTimeOffset)
{
return Instant.FromDateTimeOffset(dateTimeOffset);
}
}
}
//之後只要先 using NodaTime.Extensions,DateTeimOffset 就多了 .ToInstant() 可用
- 神奇的例子,變數為 null 也可以呼叫擴充方法
- LINQ 擴充方法一律傳回 IEnumerable<T>,支援串接式方法呼叫(Chaining Method Calls)
string[] words = { "keys", "coat", "laptop", "bottle" };
IEnumerable<string> query = words
.Where(word => word.Length > 4)
.OrderBy(word => word)
.Select(word => word.ToUpper());
- 擴充方法替代寫法
string[] words = { "keys", "coat", "laptop", "bottle" };
var tmp1 = Enumerable.Where(words, word => word.Length > 4);
var tmp2 = Enumerable.OrderBy(tmp1, word => word);
var query = Enumerable.Select(tmp2, word => word.ToUpper());
- Query Expression
LINQ 也可寫成類似 SQL 查詢語法的格式
IEnumerable<string> query = from word in words
where word.Length > 4
orderby word
select word.ToUpper();
- .Where().Select() 這種寫法沒有統一名稱,有 Method Syntax, Dot Syntax, Fluent Syntax 或 Lambda Syntax 等說法,作者 Jon 選擇叫它 Method Syntax。
- Query Expression vs Method Syntax,作者個人偏好 Method Syntax 一些,我也是。LINQ 寫法:類 SQL 查詢語法 vs 方法串接
My notes for C# in Depth part 4
Comments
# by HY
文中以下段落 .Where().Selecct() 這種寫法沒有統一名稱,有 Method Syntax, Dot Syntax, Fluent Syntax 或 Lambda Syntax 等說法,作者 Jon 選擇叫它 Method Syntax。 .Selecct() 多了一個c~~
# by Jeffrey
to HY,很好,我故意寫錯就是要測試大家有沒有認真看... (擦汗) 謝謝指正。