LINQ 的強大之處在於能夠輕鬆查詢實作 IEnumerable<T> 介面的泛型集合。然而,像 ArrayList 這類非泛型集合並未實作該介面,直接套用 LINQ 查詢會導致編譯錯誤。這並不意味著我們必須放棄 LINQ 的優雅語法,只要掌握幾個關鍵技巧,即可讓非泛型集合也能享受 LINQ 的查詢能力。
假設我們有一個儲存 Cars 物件的 ArrayList,嘗試直接使用 LINQ 查詢 BMW 車款:
ArrayList carList = new ArrayList();
carList.Add(new Cars { CarMake = "BMW", CarModel = "BMW Art", Year = 1978 });
carList.Add(new Cars { CarMake = "BMW", CarModel = "Coupe", Year = 1982 });
carList.Add(new Cars { CarMake = "Renault", CarModel = "Alpine", Year = 1972 });
carList.Add(new Cars { CarMake = "Porsche", CarModel = "Maisto", Year = 1976 });
// 編譯錯誤:ArrayList 未實作 IEnumerable<T>
var carQuery = from car in carList
where car.CarMake == "BMW"
select car;
編譯器會報錯,因為 ArrayList 僅實作 IEnumerable(非泛型版本),無法推斷元素型別。
在 from 子句中為範圍變數明確宣告型別,編譯器會自動插入轉型操作:
// C#
var cc = from Cars car in carList
where car.CarMake == "BMW"
select car;
// VB.NET
Dim cc = From car As Cars In carList
Where car.CarMake = "BMW"
Select car
這相當於對集合中的每個物件執行 (Cars)car 轉型,若集合包含非 Cars 型別物件,執行時會拋出 InvalidCastException。
Cast<T>() 擴充方法可將實作 IEnumerable 的非泛型集合轉換為 IEnumerable<T>:
// C#
var cc1 = from car in carList.Cast<Cars>()
where car.CarMake == "BMW"
select car;
// VB.NET
Dim cc1 = From car In carList.Cast(Of Cars)()
Where car.CarMake = "BMW"
Select car
此方法同樣會對所有元素嘗試轉型,遇到不相容型別時拋出例外。
OfType<T>() 會篩選集合中指定型別的元素,自動略過不相容物件,更加安全:
// C#
var cc2 = from car in carList.OfType<Cars>()
where car.CarMake == "BMW"
select car;
// VB.NET
Dim cc2 = From car In carList.OfType(Of Cars)()
Where car.CarMake = "BMW"
Select car
若集合混雜不同型別物件,OfType 僅返回符合條件的元素,不會拋出例外,是處理非泛型集合最穩健的選擇。
以下整合三種寫法的完整 C# 範例:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace QueryArraylistWithLINQ
{
public class Cars
{
public string CarMake { get; set; }
public string CarModel { get; set; }
public int Year { get; set; }
}
class Program
{
static void Main(string[] args)
{
ArrayList carList = new ArrayList();
carList.Add(new Cars { CarMake = "BMW", CarModel = "BMW Art", Year = 1978 });
carList.Add(new Cars { CarMake = "BMW", CarModel = "Coupe", Year = 1982 });
carList.Add(new Cars { CarMake = "Renault", CarModel = "Alpine", Year = 1972 });
carList.Add(new Cars { CarMake = "Porsche", CarModel = "Maisto", Year = 1976 });
// 方法一:顯式指定型別
var cc = from Cars car in carList
where car.CarMake == "BMW"
select car;
// 方法二:Cast
var cc1 = from car in carList.Cast<Cars>()
where car.CarMake == "BMW"
select car;
// 方法三:OfType(推薦)
var cc2 = from car in carList.OfType<Cars>()
where car.CarMake == "BMW"
select car;
// 輸出結果
foreach (Cars c in cc1)
Console.WriteLine($"{c.CarMake}-{c.CarModel}");
Console.ReadLine();
}
}
}
| 方法 | 語法 | 適用場景 | 風險 |
|---|---|---|---|
| 顯式型別 | from Cars car in list | 確定集合元素型別單一 | 型別不符時拋出例外 |
| Cast<T>() | list.Cast<Cars>() | 需轉換為泛型集合進一步操作 | 型別不符時拋出例外 |
| OfType<T>() | list.OfType<Cars>() | 集合可能包含混雜型別 | 安全篩選,無例外風險 |
實務上建議優先使用 OfType<T>(),既能啟用 LINQ 查詢,又能優雅處理型別不匹配情況,程式碼更健壯。