緣起
最近在 Study 一些 C# 的議題, 為避免忘記, 所以留下一些摘要記錄; 以下文章大部份係摘自 C# 5.0 in a Nutshell, 5th Edition 的第4章 "Advanced C#" 裡的 Enumeration and Iterators 說明.完整程式範例, 筆者放在 GitHub, 請由此下載.
名詞定義
- enumerator : a read-only, forward-only cursor over a sequence of values
- 實作介面
- System.Collections.IEnumerator
- System.Collections.Generic.IEnumerator<T>
- 基本上, 只要有 MoveNext() 方法及 Current 屬性 的物件, 就可以被視為 enumerator
- enumerable object : the logical representation of a sequence. It is not itself a cursor, but an object that produces cursors over itself
- 實作介面
- IEnumerable
- IEnumerable<T>
- 必須要有一個 GetEnumerator() 的方法, 以回傳 enumerator 物件
- 請注意: 上述一個是 IEnumator, IEnumator<T>, 一個是 IEnumerable, IEnumerable<T>; 不要搞混了
- 骨架如下:
class Enumerator // Typically implements IEnumerator or IEnumerator<T>
{
public IteratorVariableType Current { get {...} }
public bool MoveNext() {...}
}
class Enumerable // Typically implements IEnumerable or IEnumerable<T>
{
public Enumerator GetEnumerator() {...}
}
範例
// High-level way of iterating through the characters in the word “beer”:
foreach (char c in "beer")
Console.WriteLine (c);
// Low-level way of iterating through the same characters:
using (var enumerator = "beer".GetEnumerator())
while (enumerator.MoveNext())
{
var element = enumerator.Current;
Console.WriteLine (element);
}
上述程式碼, 共有 2 種列出所有元素的方式; 其實, foreach 敍述, 會被改以 GetEnumerator() 的方式作處理, 這是 C# 提供的 Syntactic Sugar
- 採用 foreach 敍述
- 採用 GetEnumerator() 的方式
1. String 類別實作了以下介面
public sealed class String : IComparable, ICloneable, IConvertible, IEnumerable, IComparable<string>, IEnumerable<char>, IEquatable<string>
2. String 類別實作了 GetEnumerator() 的方法, 會回傳一個 CharEnumerator 的物件
3. CharEnumerator 類別實作了以下介面
public sealed class CharEnumerator : IEnumerator, ICloneable, IEnumerator<char>, IDisposable
4. CharEnumerator 類別實作了 MoveNext() 的方法, 並有一個 Current 的屬性
如果有空, 也可以看一下 List<T> 的源碼, 與上述範例的骨架差不多.
延伸閱讀(一) Enumerable<T>
.NET Framework 另外提供了一個靜態類別 Enumerable (MSDN / 源碼), 它屬於 System.Linq 這個命名空間, 針對實作 IEnumerable<T> 的類別, 提供了一些靜態方法, 例如: Where, Average ..., 讓實作 IEnumerable<T> 的類別 得以使用 LINQ 的語法.
以下是 Where 擴充方法的原始程式碼
說明一下 ( this IEnumerable<TSource> source, Func<TSource, bool> predicate ) 的參數意義:
第1個參數: 代表呼叫該方法的物件本身, 此為擴充方法的設計規格
第2個參數: 代表一個傳入為 TSource, 回傳為 bool 的委派物件 (Func<T1, TResult> 本身就是一個 delegate)
說明一下 ( this IEnumerable<TSource> source, Func<TSource, bool> predicate ) 的參數意義:
第1個參數: 代表呼叫該方法的物件本身, 此為擴充方法的設計規格
第2個參數: 代表一個傳入為 TSource, 回傳為 bool 的委派物件 (Func<T1, TResult> 本身就是一個 delegate)
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {
if (source == null) throw Error.ArgumentNull("source");
if (predicate == null) throw Error.ArgumentNull("predicate");
if (source is Iterator<TSource>) return ((Iterator<TSource>)source).Where(predicate);
if (source is TSource[]) return new WhereArrayIterator<TSource>((TSource[])source, predicate);
if (source is List<TSource>) return new WhereListIterator<TSource>((List<TSource>)source, predicate);
return new WhereEnumerableIterator<TSource>(source, predicate);
}
以下是一個範例, 細節說明都寫在程式裡:
附帶一提, var emps2 = emps.Where(o => o.Name.StartsWith("J")); 代表 emps 呼叫 Where 擴充方法, 而傳入一個 匿名方法 o => o.Name.StartsWith("J"), 作為委派物件回呼 (callback) 之用, 傳入的 o 是 Employee 的資料型態, 而 StartsWith(...) 的回傳值是一個 bool.
class Employee
{
public string Id { get; set; }
public string Name { get; set; }
}
class Program
{
static void Main(string[] args)
{
//建立測試資料
List<Employee> emps = new List<Employee>()
{ new Employee { Id = "001", Name="Jasper" }
, new Employee { Id = "002", Name="Judy" }
, new Employee { Id = "003", Name="John" }
, new Employee { Id = "004", Name="Anita"}
, new Employee { Id = "005", Name="Belle"}
};
//說明:
//1. List<Employee> 是採用 List<T> 這個泛型的 constructed type or closed type; 代表程式裡用到的真正資料型態
//2. List<T> 實作 IEnumerable<T>
//3. Enumerable 靜態類別提供 IEnumerable<T> 的擴充方法, 例如: Where, Average ... 等
//4. 因為 Where 是擴充方法, 所以可以直接用 .Where(...) 的方式作處理
//參考一下:
//可以順便查一下 Where 對應的源碼, 可以發現, 它是由 Emumeratable 這個靜態類別所提供的擴充方法
//http://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,e73922753675387a
//Lambda Expression
Console.WriteLine("===== Lambda =====");
var emps2 = emps.Where(o => o.Name.StartsWith("J"));
foreach (var item in emps2)
{
Console.WriteLine("Id:{0}, Name:{1}", item.Id, item.Name);
}
//LINQ Statement
Console.WriteLine("===== LINQ =====");
var query = from o in emps
where o.Name.StartsWith("J")
select o;
foreach (var item in query)
{
Console.WriteLine("Id:{0}, Name:{1}", item.Id, item.Name );
}
Console.ReadLine();
////Output:
//===== Lambda =====
//Id:001, Name:Jasper
//Id:002, Name:Judy
//Id:003, Name:John
//===== LINQ =====
//Id:001, Name:Jasper
//Id:002, Name:Judy
//Id:003, Name:John
}
}
延伸閱讀(二) IEnumerable<T> and IQueryable<T>
如上討論, IEnumerable<T> 必須要有一個實際的已存在集合, 如果在本地沒有問題; 但如果在遠端, 勢必要把遠端所有的資料都拉到本地, 才能作處理, 如果資料量很大, 效能上就會受到影響.
為了處理這個問題, 微軟提供了一個 IQueryable<T> 的界面作處理.
當使用者對 IQueryable<T> 進行操作時, Queryable 類別會透過呼叫 IQueryable<T> 的 Provider 屬性 (這個屬性存放的是實作 IQueryProvider 的物件), 利用該物作, 進行資料存取.
為了處理這個問題, 微軟提供了一個 IQueryable<T> 的界面作處理.
當使用者對 IQueryable<T> 進行操作時, Queryable 類別會透過呼叫 IQueryable<T> 的 Provider 屬性 (這個屬性存放的是實作 IQueryProvider 的物件), 利用該物作, 進行資料存取.
註:
1. IQureyable<T> 繼承自 IEnumerable<T>
2. IEnumerable<T> 有 Enumerable<T> 這個類別提供 extension method
3. IQueryable<T> 有 Queryable<T> 這個類別提供 extension method
在 CodeProject 有2篇文章探討 IEnumerable<T> 與 IQueryable<T>
- IEnumerable vs IQueryable
- 主要繪製了 IEnumerable<T> 與 IQueryable<T> 在進行資料過慮時的簡圖; 同時也有一段影片作 demo
- IEnumerable vs IQueryable
- 製作了一張比較表
黑暗執行緒 也有一篇文章, 將 IEnumerable<T> 與 IQueryable() 作了深入的追踨.
- 關於IQueryable<T>特性的小實驗
- 這篇將 IEnumerable<T> 與 IQueryable<T> 以北風資料庫的 Product table 作為範例; 利用 Visual Studio 偵錯, 並在 SQL Profile 觀察實際傳送至資料庫執行的 SQL 指令; 發現 IQueryable<T> 是在 Server 端作過濾, 再將結果傳回 Client 端, 故若為資料庫存取, 應採用 IQueryable<T>
以下綜合上述文章, 並依 黑暗執行緒 的那篇文章的程序, 重作一次 (只是對象換成 Employees 這個 table)
IEnumerable<T>
程式碼:private static void TestIEnumerable()
{
using (NorthwindEntities ctx = new NorthwindEntities())
{
IEnumerable<Employee> emps = ctx.Employees;
int count = emps.Where(x => x.EmployeeID == 2).Count();
Console.WriteLine("Count={0}", count);
}
}
相對應的 SQL 語句:
SELECT
[Extent1].[EmployeeID] AS [EmployeeID],
[Extent1].[LastName] AS [LastName],
[Extent1].[FirstName] AS [FirstName],
[Extent1].[Title] AS [Title],
[Extent1].[TitleOfCourtesy] AS [TitleOfCourtesy],
[Extent1].[BirthDate] AS [BirthDate],
[Extent1].[HireDate] AS [HireDate],
[Extent1].[Address] AS [Address],
[Extent1].[City] AS [City],
[Extent1].[Region] AS [Region],
[Extent1].[PostalCode] AS [PostalCode],
[Extent1].[Country] AS [Country],
[Extent1].[HomePhone] AS [HomePhone],
[Extent1].[Extension] AS [Extension],
[Extent1].[Photo] AS [Photo],
[Extent1].[Notes] AS [Notes],
[Extent1].[PhotoPath] AS [PhotoPath],
[Extent1].[ReportsTo] AS [ReportsTo]
FROM [dbo].[Employees] AS [Extent1]
架構圖:
IQueryable<T>
程式碼:
private static void TestIQueryable()
{
using (NorthwindEntities ctx = new NorthwindEntities())
{
IQueryable<Employee> emps = ctx.Employees;
int count = emps.Where(x => x.EmployeeID == 2).Count();
Console.WriteLine("Count={0}", count);
}
}
相對應的 SQL 語句:
SELECT
[GroupBy1].[A1] AS [C1]
FROM ( SELECT
COUNT(1) AS [A1]
FROM [dbo].[Employees] AS [Extent1]
WHERE 2 = [Extent1].[EmployeeID]
) AS [GroupBy1]
架構圖:
說明: IQueryable<T> 係在 Server 端作完過濾, 才將資料傳回. 上述會將所有 EmployeeID==2 的資料在資料庫端就進行 Where(), 並取得其 Count().
綜上所述, 在資料庫相關的環境下, 用 IQueryable<T> 的效能會比 IEnumerable<T> 來得好.
綜上所述, 在資料庫相關的環境下, 用 IQueryable<T> 的效能會比 IEnumerable<T> 來得好.
總結
本篇主要在釐清 IEnumerable 與 IEnumerator 的不同. 也介紹 Enumerable 這個靜態類別, 並記錄了一些網路上 IEnumerable 與 IQuery 進行比較的資料.下圖取自 C# 5.0 in a Nutshell, 5th Edition 的第7章, 用以表達整個 IEnumerator 及 IEnumerator<T> 的繼承架構.
![]() |
| Collection Interfaces |
前述有關 Syntactic Sugar 的部份, 有另外一篇文章可以參考, 它將 C# compile 的過程, 大致繪製如下圖; 亦即, 編譯的過程中, 會有一個階段, 將 Fancy C# Code 轉為 Regular C# Code.
![]() |
| C# Compile 簡要過程 |
參考文件
- {Book} C# 5.0 in a Nutshell, 5th Edition Ch04, Ch07
- 針對集合的操作有深入的說明
- {LittleLin's Scrapbook} [C#][筆記] IEnumerable、IEnumerator 與 IEnumerable<T>、IEnumerator<T>
- 針對 IEnumerable、IEnumerator 與 IEnumerable<T>、IEnumerator<T> 有範例說明
- {MSDN Blogs} C# Delegates, Actions, Funcs, Lambdas–Keeping it super simple
- 針對 Delegates, Actions, Funcs, Lambdas 的程式撰寫方式有作說明
- {Huan-Lin 學習筆記} C# 筆記:從 Lambda 表示式到 LINQ
- 針對 Lambda Expression 有詳細的說明
- IEnumerable vs IQueryable
- 主要繪製了 IEnumerable<T> 與 IQueryable<T> 在進行資料過慮時的簡圖; 同時也有一段影片作 demo
- IEnumerable vs IQueryable
- 製作了一張比較表
- {黑暗執行緒} 關於IQueryable<T>特性的小實驗
- 這篇將 IEnumerable<T> 與 IQueryable<T> 以北風資料庫的 Product table 作為範例; 利用 Visual Studio 偵錯, 並在 SQL Profile 觀察實際傳送至資料庫執行的 SQL 指令; 發現 IQueryable<T> 是在 Server 端作過濾, 再將結果傳回 Client 端, 故若為資料庫存取, 應採用 IQueryable<T>
版本修訂
- 2015.01.22 加入 延伸閱讀(二) IEnumerable<T> and IQueryable<T> 的比較
- 2015.01.17 首次發行
.



.jpg)
受益良多,謝謝您的分享
回覆刪除不客氣, 很高興能夠幫助到你.
刪除學習了,謝謝您 : )
回覆刪除不客氣, 很高興能夠幫助到你.
刪除清楚明瞭!感謝分享~
回覆刪除不客氣, 很高興能夠幫助到你.
刪除