iT邦幫忙

DAY 12
3

分享一些學習心得系列 第 12

LINQ自學筆記-打地基-匿名型別

匿名型別可以讓我們在不事先宣告一個類別結構,就可以建立一個自訂的型別於執行時期使用,搭配物件初始化設定式和型別推斷,開發人員可以省去非常多繁瑣的程式碼。
自學筆記這系列是我自己學習的一些心得分享,歡迎指教。這系列的分享,會以 C# + 我比較熟的 Net 3.5 環境為主。
另外本系列預計至少會切成【打地基】和【語法應用】兩大部分做分享。打地基的部分,講的是 LINQ 的組成元素,這部分幾乎和 LINQ 無關,反而是 C# 2.0、C# 3.0 的一堆語言特性,例如:型別推斷、擴充方法、泛型、委派等等,不過都會把分享的範圍限制在和 LINQ 應用有直接相關功能。
PS. LINQ 自學筆記幾乎所有範例,都可直接複製到 LINQPad 4 上執行(大多是用 Statements 和 Program 模式)。因為它輕巧好用,功能強大,寫範例很方便,請大家自行到以下網址下載最新的 LINQPad:http://www.LINQpad.net/。
MSDN 中,對於 C# 匿名型別的定義如下:
匿名型別提供便利的方式將唯讀屬性集封裝至單一物件,而不必先明確定義型別。 型別名稱是由編譯器產生,而且在來源程式碼層級中無法使用。 每個屬性的型別是由編譯器推斷。

也就是說,匿名型別(Anonymous types)可讓開發人員建立物件執行個體,不需要先撰寫型別的類別定義。匿名型別會直接繼承自 Object,並且包含我們在建立匿名型別時所設定的屬性。

使用匿名型別,通常(也可以說是必須)得搭配「型別推斷」技術,因為如同 MSDN 所言,型別名稱是由編譯器產生,所以開發人員在撰寫程式時,不可能知道匿名型別會被編譯為什麼名稱,因此利用型別推斷功能,才有辦法使用它。另外建立匿名型別時,都會予以設定屬性名稱和屬性值,這時候就是搭配「物件初始化設定式」的技術來解決。

下面是建立匿名型別的一般用法(搭配型別推斷和物件初始化設定式):
C#:

var a = new { Name = "Leo", Score = 90 };

VB:

Dim product = New With {.Name = "Leo", .Score= 90}

請注意建立 VB 匿名型別時,要加上 With,而且型別屬性要用「.」開頭,這兩點是常見的錯誤。

接著我們來驗證一件事,MSDN 定義所說的,C# 的匿名型別之屬性是唯讀屬性:

var obj = new {Name = "Leo"};
obj.Name = "Rose";
obj.Name.Dump();

但有趣的事情來了,在 VB 中,匿名型別的屬性預設並非「唯讀」喔!除非我們在屬性名稱前加上「Key」關鍵字:

Dim obj = New With {Key .Name = "Leo", .Score= 90}
'obj.Name = "Rose"
obj.Name.Dump()
obj.Score = 95
obj.Score.Dump()


上述範例,Name 屬性有加上 Key 關鍵字,但是 Score 沒有,所以若把第二行的註解拿掉,就會出現「屬性 'Name' 是 'ReadOnly'。」的編譯錯誤。

使用匿名型別還要注意一件事情:編譯器如何判讀匿名型別是否屬於同一個類別?基本原則如下:

  1. 屬性數量相同。
  2. 屬性指定順序相同。
  3. 屬性的型別相同。
  4. 屬性的名稱相同。

注意囉,所以下述範例,大家猜猜會有幾個匿名型別出現:

void Main()
{
    var s1 = new Student(){StudentName = "Leo", Score = 90};
    var s2 = new {StudentName = "Leo", Score = 90};
    var s3 = new {s1.StudentName, s1.Score};
    s1.GetType().Dump("S1");
    s2.GetType().Dump("S2");
    s3.GetType().Dump("S3");
    
    var s4 = new {Name = "Rose", Score = 60};
    var s5 = new {s4.Name, s4.Score};
    var s6 = new {s4.Score, s4.Name};
    s4.GetType().Dump("S4");
    s5.GetType().Dump("S5");
    s6.GetType().Dump("S6");
}

public class Student 
{
    public string StudentName {get; set;}
    public int Score {get; set;}
}


正解是三個(AnonymousType0~2)。S1 是具名型別,但是 S2 和 S3 都是兩個屬性的匿名型別,且指派順序和名稱都一樣,因此這兩個匿名型別是同一個類別(AnonymousType0);S4、S5 也符合上述的原則,所以也是同一個類別(AnonymousType1),但是 S6 因為指派屬性的順序對調了,所以變成新的匿名型別(AnonymousType2)。

在 LINQ 中,使用匿名型別做為取出資料的資料型別,是很基本的作業,以 DB 的 Sql script 來對比,匿名型別就是從一個有 10 個欄位的資料表中,只查詢其中 5 個欄位出來,若不使用匿名型別,那就是 「Select * From TableName」的效果了。

對了,建立匿名型別時不設定屬性名稱,編譯不會有問題,但是似乎沒有實際的用途:


上一篇
LINQ自學筆記-打地基-物件和集合初始設定式
下一篇
LINQ自學筆記-打地基-LINQ語法123
系列文
分享一些學習心得30

尚未有邦友留言

立即登入留言