本篇介紹Linq的延遲執行和查詢非泛型集合。
延遲執行
從Linq的查詢過程看,不斷有新類型、集合的構建、釋放,感覺效率不高,但Linq的延遲執行機制將大大改善常規操作加載整個數據源導致的效率低下的狀況。
示例:
static void Main()
{
int[] collection = { 10, 11, 12, 13, 14 };
Console.WriteLine("Begin query collection:" + DateTime.Now);
var result = from i in collection
select DoubleInt(i);
Console.WriteLine("End query collection & Begin output:" + DateTime.Now);
foreach (var i in result)
{
Console.WriteLine("Result:" + i + " Time:" + DateTime.Now.ToString());
}
Console.WriteLine("End output:" + DateTime.Now);
}
static int DoubleInt(int i)
{
Console.WriteLine("The parameter is:" + i);
Thread.Sleep(1000);
return i * 2;
}
// 輸出為:// Begin query collection:2008-9-12 22:41:50
// End query collection & Begin output:2008-9-12 22:41:50
// The parameter is:10
// Result:20 Time:2008-9-12 22:41:51
// The parameter is:11
// Result:22 Time:2008-9-12 22:41:52
// The parameter is:12
// Result:24 Time:2008-9-12 22:41:53
// The parameter is:13
// Result:26 Time:2008-9-12 22:41:54
// The parameter is:14
// Result:28 Time:2008-9-12 22:41:55
// End output:2008-9-12 22:41:55
從輸出結果可以看出,查詢時並沒有立即計算結果,而是輸出時才進行計算。
其實在構造查詢語句時,只是傳遞的相應操作的委托,並沒有真正執行查詢操作,真正的執行是在 IEnumerator<T> 的 GetEnumerator() 方法中執行的,每取得一個元素就執行一次。延遲執行還有個好處是改變數據源時不需要構造新的查詢語句即可執行獲得想要的結果。
示例:
static void Main()
{
// 原始集合
int[] collection = { 10, 11, 12, 13, 14 };
// 查詢集合
var result = from i in collection
select DoubleInt(i);
// 輸出原集合
foreach (var i in result)
{
Console.WriteLine(i);
}
Console.WriteLine("Change data source:");
// 改變集合
for (var i = 0; i < collection.Length; i++)
{
collection[i] += 10;
}
// 輸出改變後的集合
foreach (var i in result)
{
Console.WriteLine(i);
}
}
static int DoubleInt(int i)
{
return i * 2;
}
// 結果:// 20
// 22
// 24
// 26
// 28
// Change data source:
// 40
// 42
// 44
// 46
// 48
為了更清楚的演示延遲執行的執行時間,自己實現 IEnumerator<T> 接口,通過下面代碼可以看出,查詢操作是在 GetEnumerator() 方法中執行的。
static void Main()
{
int[] collection = { 10, 11, 12, 13, 14 };
var result = from i in GetEnumerator(collection)
select i;
foreach (var i in result)
{
Console.WriteLine(i);
}
}
public static IEnumerable<int> GetEnumerator(int[] collection)
{
foreach (var i in collection)
{
Console.WriteLine("Compute:" + i);
yield return DoubleInt(i);
}
}
static int DoubleInt(int i)
{
Console.WriteLine("The parameter is:" + i);
return i * 2;
}
// 輸出:// Compute:10
// The parameter is:10
// 20
// Compute:11
// The parameter is:11
// 22
// Compute:12
// The parameter is:12
// 24
// Compute:13
// The parameter is:13
// 26
// Compute:14
// The parameter is:14
// 28
延遲執行的優點是不需要同時加載整個數據源,執行占用資源少、效率高,試想如果數據源非常大,全部加載效率會多低。
當然,某些時候,延遲執行也會導致數據不一致等等,想要關閉延遲執行,可以用ToList<T>()、ToArray<T>()等方法立即執行查詢操作。
前面介紹的集合操作時會影響延遲操作,因此在執行集合操作時應特別注意。
下面的例子演示立即執行。
static void Main()
{
int[] collection = { 10, 11, 12, 13, 14 };
Console.WriteLine("Begin query collection:" + DateTime.Now);
var result = from i in collection
select DoubleInt(i);
Console.WriteLine("End query collection & Begin output:" + DateTime.Now);
foreach (var i in result.ToList())
{
Console.WriteLine("Result:" + i + " Time:" + DateTime.Now.ToString());
}
Console.WriteLine("End output & Begin get collection count:" + DateTime.Now);
// 集合操作:取元素的數量
Console.WriteLine("元素數量為:" + result.Count());
Console.WriteLine("End get collection count:" + DateTime.Now);
}
static int DoubleInt(int i)
{
Console.WriteLine("The parameter is:" + i);
Thread.Sleep(1000);
return i * 2;
}
// 輸出結果:// Begin query collection:2008-9-12 23:36:34
// End query collection & Begin output:2008-9-12 23:36:34
// The parameter is:10
// The parameter is:11
// The parameter is:12
// The parameter is:13
// The parameter is:14
// Result:20 Time:2008-9-12 23:36:39
// Result:22 Time:2008-9-12 23:36:39
// Result:24 Time:2008-9-12 23:36:39
// Result:26 Time:2008-9-12 23:36:39
// Result:28 Time:2008-9-12 23:36:39
// End output & Begin get collection count:2008-9-12 23:36:39
// The parameter is:10
// The parameter is:11
// The parameter is:12
// The parameter is:13
// The parameter is:14
// 元素數量為:5
// End get collection count:2008-9-12 23:36:44
查詢非泛型集合
在 .NET1.X 時代不支持泛型,構造集合時都要轉換或裝箱為Object類型,Linq 查詢是以泛型為基礎的,在老系統升級或整合時要進行非泛型集合操作。Enumerable的Cast<T>() 和OfType<T>()擴展方法使非泛型集合的 Linq 查詢成為可能。ArrayList list = new ArrayList();
list.Add(1);
list.Add(2);
list.Add(3);
list.Add(4);
list.Add(5);
var result = from i in list.Cast<int>()
select i;
foreach (var i in result)
{
Console.WriteLine(i);
}
// result:
// 1
// 2
// 3
// 4
// 5
如果不想顯式調用Cast<T>()方法,可以在元素前面指定類型。上面的例子變為:
ArrayList list = new ArrayList();
list.Add(1);
list.Add(2);
list.Add(3);
list.Add(4);
list.Add(5);
var result = from int i in list
select i;
foreach (var i in result)
{
Console.WriteLine(i);
}
我們知道,非泛型不是類型安全的,可以在非泛型的ArrayList中同時插入整型、字符串,那麼上面的方法進行類型轉換會拋出異常,影響操作的繼續進行,OfType<T>()可以一定程度上避免這個問題。如下面的示例會選擇適當的類型而忽略不匹配類型。
ArrayList list = new ArrayList();
list.Add(1);
list.Add(2);
list.Add("abc");
list.Add(3);
list.Add(4);
var result = from int i in list.OfType<int>()
select i;
foreach (var i in result)
{
Console.WriteLine(i);
}
// result:
// 1
// 2
// 3
// 4
如果這個例子中的集合用上面的Cast<T>()或隱式轉換將會拋出異常。