【本系列是我的 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 如何推斷元素型別?
    1. 先找出一組侯選型別(Candidate Type)
    2. 針對每個元素試試能否成功隱含轉型,淘汰失敗者
    3. 篩選完若只剩一個型別吻合,即為推斷元素型別(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,很好,我故意寫錯就是要測試大家有沒有認真看... (擦汗) 謝謝指正。

Post a comment