Spring4D 的集合框架提供強大的泛型集合操作功能,效能表現也相當出色。核心介面 IEnumerable<T> 提供迭代、查詢和篩選等基礎功能,並支援 LINQ 風格的鏈式呼叫。ICollection<T> 則擴充套件了集合操作功能,包含新增、移除和清空等方法,並提供事件通知機制。IList<T> 則進一步支援有序集合的操作,允許透過索引存取和插入元素,並提供排序功能。瞭解這些介面的特性和使用方法,能有效提升 Delphi 開發效率,特別是在處理大量資料的應用場景中。

Spring4D 集合框架詳解:列舉、集合與列表

重點升級:從 Spring v1 遷移到 Spring v2

在撰寫本章內容時,Spring v2 正式釋出。對於已經熟悉 Spring v1 的開發者來說,升級過程中可能會遇到一些問題。本文將協助您解決這些問題:

  • Spring v2 已將 System.Generics.Collections 單元的功能重新實作在 Spring.Collections 中,建議移除 Delphi 原生的單元以避免衝突。需要注意的是,Spring v2 重新定義了 TPair 型別,可能會導致與原生型別衝突。
  • Spring v2 重新定義了 Delphi 的 TDictionaryOwnership 型別(位於 Spring 單元)。
  • Spring v2 的篩選函式現在接受 const 引數,因此可能需要更新篩選函式的定義。
  • 雙向字典 IBidiDictionary 的介面已被簡化,其具體細節請參考本章後續的「集合與字典」章節。

集合框架的核心概念

本章重點介紹 Spring4D 函式庫中最重要的部分之一:強大且豐富的集合支援。所有與集合相關的單元都位於 Source\Base\Collections 子資料夾中。

何謂集合?

在探討 Spring4D 的集合框架之前,我們需要先定義「集合」的概念。簡單來說,集合是一組相同型別的資料專案,並透過某種 API 以有組織的方式存取(讀取、寫入和刪除)這些資料專案。不同的集合型別提供了不同的 API,因此可以根據具體需求選擇合適的集合。

Delphi 中的 TList 是一種指標的集合,而 TList<T> 則是一種泛型集合,用於儲存特定型別的資料專案。

Spring4D 集合的特點

Spring4D 的集合是根據介面實作的,其核心建立在泛型型別和介面上。這些介面經過精心設計,以避免在使用多種不同資料型別的泛型集合時導致編譯時間變慢或可執行檔膨脹。同時,這些集合的效能至少與 Delphi 原生實作相當,在某些操作上甚至更快。

部分介面(如 IEnumerableICollection)也提供了非泛型的版本,用於儲存 TValue 專案。這些非泛型版本根據泛型實作,主要用於資料序列化等場景,此時資料的具體型別可能事先未知。

介面繼承關係與設計理念

Spring4D 的集合 API 被組織成多個介面,這些介面均衍生自 IEnumerable(非泛型版本)或 IEnumerable<T>(泛型版本)。下圖展示了泛型集合介面的完整繼承關係:

此圖示展示了 Spring4D 集合介面的繼承結構,例如,我們可以將任何 ISortedDictionary<K,V> 視為 IMap<K,V>ICollection<T>(其中 T 是 TPair<K,V>)以及 IEnumerable<T>。這種設計允許我們根據具體的使用場景,在適當的抽象層級上操作集合。

舉例來說,如果我們需要一個函式來遍歷集合中的所有元素並進行處理,我們不需要直接使用 ISet<T> 介面。由於我們只需要遍歷集合,因此可以將引數型別設為 IEnumerable<T>,這樣我們的程式碼就可以相容於任何型別的集合。

圖表翻譯:

此圖示展示了 Spring4D 中各個集合介面之間的繼承關係,從最基礎的 IEnumerable<T> 到更專門的集合型別,如 IList<T>IDictionary<K, V>。這種設計使得不同型別的集合可以共用一套操作介面,提高了程式碼的靈活性與可重用性。

IEnumerable 介面詳解

IEnumerable<T> 是 Spring4D 集合框架中最基礎且最重要的介面之一,它提供了對唯讀資料序列的支援,並且與 Delphi 的 for .. in 迴圈陳述式相容。這使得任何 Spring4D 集合都可以輕鬆地參與這種非常有用的語法結構。

以下是一個範例程式碼,展示如何使用 TEnumerable.Range 方法建立一個包含數字 1 到 5 的 IEnumerable<Integer>,並透過 for .. in 迴圈列舉其內容:

procedure TfrmEnumerable.btnForInClick(Sender: TObject);
var
  enum: IEnumerable<Integer>;
  i: Integer;
begin
  enum := TEnumerable.Range(1, 5);
  for i in enum do
    ListBox1.Items.Add(IntToStr(i));
end;

程式碼解密:

  1. TEnumerable.Range(1, 5):此方法呼叫建立了一個包含數字 1 到 5 的可列舉序列。
  2. for i in enum do:利用 Delphi 的 for .. in 語法遍歷 enum 中的所有元素。
  3. ListBox1.Items.Add(IntToStr(i)):將每個數字轉換為字串後加入到列表框中。

值得注意的是,IEnumerable<T> 本身並不保證元素的順序,這取決於具體的集合實作。如果某個集合是有序的(例如排序過的),那麼列舉時將按照排序順序傳回元素;否則,順序是不確定的。

雖然 Spring v2 在某些未排序的集合中保持了插入順序,但依賴具體實作細節並不是好的做法。正如依賴倒置原則所說,我們應該依賴抽象而非具體實作。

資料查詢與篩選

IEnumerable<T> 不僅支援基本的列舉功能,還提供了豐富的資料查詢與篩選方法。透過該介面定義的大量方法,我們可以輕鬆地對集合進行複雜的操作。

例如,我們可以使用 LINQ 風格的方法鏈來對集合進行查詢、過濾和轉換等操作。這些方法大多傳回新的 IEnumerable<T>,因此可以靈活組合使用。

與實務應用

在未來的開發中,Spring4D 的集合框架將繼續發揮重要作用,尤其是在需要高效處理大量資料的應用場景中。開發者應當善用其提供的豐富功能和靈活的介面設計,以提升程式碼的可讀性和可維護性。

此外,隨著軟體需求的不斷變化,我們需要持續關注 Spring4D 社群的最新發展,以充分利用其新特性和最佳實踐,不斷最佳化我們的應用程式。

深入理解 Spring Framework 中的 IEnumerable 查詢功能

Spring Framework 為 Delphi 開發者提供了豐富的集合操作功能,特別是在 IEnumerable<T> 介面的實作上。本文將探討如何使用 Spring 的查詢功能來處理集合資料,並透過具體範例展示其強大之處。

查詢方法總覽

IEnumerable<T> 介面提供了一系列查詢方法,能夠對原始集合進行過濾、排序、跳過或擷取元素等操作。以下是完整的查詢方法列表:

function Concat(const second: IEnumerable<T>): IEnumerable<T>;
function Memoize: IEnumerable<T>;
function Ordered: IEnumerable<T>; overload;
function Ordered(const comparer: IComparer<T>): IEnumerable<T>; overload;
function Ordered(const comparer: TComparison<T>): IEnumerable<T>; overload;
function Reversed: IEnumerable<T>;
function Shuffled: IEnumerable<T>;
function Skip(count: Integer): IEnumerable<T>;
function SkipLast(count: Integer): IEnumerable<T>;
function SkipWhile(const predicate: Predicate<T>): IEnumerable<T>; overload;
function SkipWhile(const predicate: Func<T, Integer, Boolean>): IEnumerable<T>; overload;
function Take(count: Integer): IEnumerable<T>;
function TakeLast(count: Integer): IEnumerable<T>;
function TakeWhile(const predicate: Predicate<T>): IEnumerable<T>; overload;
function TakeWhile(const predicate: Func<T, Integer, Boolean>): IEnumerable<T>; overload;
function Where(const predicate: Predicate<T>): IEnumerable<T>; overload;
function Where(const predicate: Func<T, Integer, Boolean>): IEnumerable<T>; overload;

內容解密:

這些查詢方法允許開發者以宣告式的方式處理集合資料,而無需編寫繁瑣的迴圈邏輯。特別值得注意的是,部分方法接受 Func<T>Predicate<T> 引數,這些型別與 Delphi 的 TFunc<T>TPredicate<T> 功能等效,但使用 const 引數以提高執行效率,特別是在處理字串、陣列、記錄或介面時。

查詢範例詳解

讓我們透過幾個具體的範例來瞭解如何使用 IEnumerable<T> 的查詢方法。

範例 1:使用 Where 方法過濾資料

function TfrmEnumerable.IsDivisibleBy3(const i: integer): boolean;
begin
  Result := (i mod 3) = 0;
end;

procedure TfrmEnumerable.btnIEnumerable1Click(Sender: TObject);
var
  enum: IEnumerable<integer>;
begin
  enum := TEnumerable.Range(1, 20);
  ListBox1.Items.Add('Where/3: ' + Join(',', enum.Where(IsDivisibleBy3)));
  ListBox1.Items.Add('Where/5: ' + Join(',', enum.Where(function (const i: integer): boolean
    begin
      Result := (i mod 5) = 0
    end)));
end;

內容解密:

此範例展示了兩種使用 Where 方法的方式。第一種使用了一個名為 IsDivisibleBy3 的獨立函式,第二種則直接在呼叫 Where 時定義了一個匿名函式。兩者都達到了過濾的目的,但匿名函式使得程式碼更加簡潔。

鏈式查詢與延遲執行

Spring 的查詢功能支援鏈式呼叫,並且採用延遲執行的策略,這意味著查詢只有在真正需要結果時才會被執行。這種設計大大提高了程式的效能。

procedure TfrmEnumerable.btnIEnumerable2Click(Sender: TObject);
var
  enum: IEnumerable<integer>;
begin
  enum := TEnumerable.Range(1, 10);
  ListBox1.Items.Add('Skip: ' + Join(',', enum.Skip(3).SkipLast(3).Reversed));
end;

內容解密:

此範例展示瞭如何透過鏈式呼叫實作複雜的查詢邏輯。首先生成 1 到 10 的數字序列,然後跳過前三個元素,再跳過最後三個元素,最後反轉剩餘的元素順序。整個過程沒有使用任何顯式的迴圈,展現了宣告式程式設計的強大之處。

延遲執行的優勢

procedure TfrmEnumerable.btnIEnumerable4Click(Sender: TObject);
var
  enum: IEnumerable<integer>;
  filtered: IEnumerable<integer>;
  i: integer;
begin
  enum := TEnumerable.Range(1, 200);
  filtered := enum.Where(function (const value: integer): boolean
    begin
      Result := Odd(value);
      ListBox1.Items.Add('? ' + IntToStr(value));
    end).Take(3);
  ListBox1.Items.Add('Start');
  for i in filtered do
    ListBox1.Items.Add('=> ' + IntToStr(i));
end;

內容解密:

此範例演示了延遲執行的特性。WhereTake 方法並不會立即執行,而是在 for..in 迴圈迭代 filtered 時才開始執行。這種機制避免了不必要的計算,從而提升了效能。

深入理解IEnumerable介面:查詢函式以外的功能

在前面的章節中,我們已經詳細探討了查詢函式的使用方法。本文將進一步介紹IEnumerable<T>介面中其他重要的功能,包括如何存取單一元素、執行測試以及使用聚合函式等。

存取單一元素

IEnumerable<T>介面提供了一系列函式,用於傳回滿足特定條件的單一元素。這些函式包括:

  • FirstLast:用於取得序列中的第一個或最後一個元素。
  • MinMax:用於找出序列中的最小值或最大值。
  • AggregateSum:用於對序列執行聚合運算。
  • Single:用於傳回序列中唯一的元素,如果序列為空或包含多個元素,則會引發異常。

以下程式碼範例示範瞭如何使用Single函式:

function TfrmEnumerable.DivisibleBy(const num: integer): Predicate<integer>;
begin
  Result := function(const i: integer): boolean
            begin
              Result := (i mod num) = 0;
            end;
end;

procedure TfrmEnumerable.btnIEnumerable3Click(Sender: TObject);
begin
  ShowMessage(
    TEnumerable.Range(1, 20)
    .Where(DivisibleBy(3))
    .Where(DivisibleBy(5))
    .Single.ToString);
end;

內容解密:

  • DivisibleBy函式是一個工廠函式,用於建立一個判斷數字是否能被特定數字整除的謂詞(Predicate)。
  • btnIEnumerable3Click事件處理程式中,我們使用TEnumerable.Range生成一個從1到20的數字序列。
  • 透過兩次Where篩選,序列中只保留能被3和5整除的數字,即15。
  • Single函式傳回序列中唯一的元素,即15,並將其轉換為字串後顯示在訊息框中。

測試函式

IEnumerable<T>介面還提供了一組測試函式,用於檢查序列是否滿足特定的條件。這些函式包括:

  • AllAny:用於檢查序列中的所有元素或任意元素是否滿足特定條件。
  • AtLeastAtMostBetween:用於檢查序列中的元素數量是否滿足特定條件。
  • Contains:用於檢查序列中是否包含特定的元素。
  • EqualsTo:用於比較兩個序列是否相等。
function All(const predicate: Predicate<T>): Boolean;
function Any: Boolean; overload;
function Any(const predicate: Predicate<T>): Boolean; overload;
function AtLeast(count: Integer): Boolean;
function AtMost(count: Integer): Boolean;
function Between(min, max: Integer): Boolean;
function Contains(const value: T): Boolean; overload;

內容解密:

  • 這些測試函式傳回一個布林值,指示序列是否滿足特定的條件。
  • 例如,All函式檢查序列中的所有元素是否滿足特定條件,而Any函式檢查序列中是否存在至少一個元素滿足特定條件。

Spring4D 中的集合介面:IEnumerable、ICollection 和 IList

在 Delphi 的 Spring4D 函式庫中,集合介面提供了一套豐富且強大的工具,用於處理資料集合。本文將探討 IEnumerable<T>ICollection<T>IList<T> 這三個核心介面,並介紹它們的主要功能和使用方法。

IEnumerable 介面

IEnumerable<T> 是 Spring4D 集合架構中最基礎的介面,代表一個可以迭代的資料序列。它提供了多種方法用於查詢和操作資料,包括:

查詢與過濾

  • Contains:檢查集合中是否包含特定元素,可自訂比較器。
  • EqualsTo:比較集合與另一個集合或陣列是否相等。
  • Exactly:檢查集合是否恰好包含指定數量的元素。
  • IsEmpty:檢查集合是否為空。

內容解密:

這些方法允許開發者對集合進行基本查詢和驗證。Contains 方法的時間複雜度取決於具體實作,例如在未排序的列表中為 O(n),而在排序列表中可達 O(log n)。

其他實用功能

  • ToArray:將集合轉換為動態陣列,便於與 RTL 函式互動。
  • ForEach:對集合中的每個元素執行指定的動作(匿名程式)。
  • Count:傳回集合中的元素數量。
  • ElementAtTryGetElementAt/ElementAtOrDefault:根據索引存取元素,需注意不同實作的時間複雜度差異。

內容解密:

ToArray 方法對於需要與其他函式庫或 API 互動時非常有用。ForEach 提供了一種簡潔的方式來對集合元素執行操作。ElementAt 的時間複雜度依實作而異,例如在雜湊集合中可達 O(1),但預設實作通常為 O(n)。

ICollection 介面

ICollection<T> 代表一個無序的「袋子」,用於存放和移除元素。它繼承自 IEnumerable<T>,並新增了以下功能:

  • 新增元素:AddAddRange
  • 移除元素:ExtractRemoveRemoveAllRemoveRange
  • 清空集合:Clear
  • 複製或行動資料:CopyToMoveTo
  • 事件通知:OnChanged

內容解密:

這些方法提供了對集合內容的全面控制。OnChanged 事件允許監聽集合變化的通知。

IList 介面

IList<T> 代表一個有序的集合,允許根據索引存取元素。它繼承自 ICollection<T>,並新增了以下功能:

  • 根據索引插入或存取元素
  • 對列表進行排序

內容解密:

IList<T> 的介面設計與 Delphi 的 TList<T> 類別高度相似,使得從 TList<T> 遷移到 IList<T> 成為可能,只需更改列表的建立和銷毀邏輯。

範例程式碼

procedure TfrmIList.btnIListClick(Sender: TObject);
var
  i: integer;
  list: IList<integer>;
  loc: integer;
begin
  list := TCollections.CreateList<integer>;
  for i := 1 to 5 do list.Add(i);
  list.AddRange(list);
  list.Insert(5, 6);
  ListBox1.Items.Add('IList: ' + Join(' ', list.ToArray));
  list.Sort;
  ListBox1.Items.Add('Sorted: ' + Join(' ', list.ToArray));
  ListBox1.Items.Add('Pos(5): ' + IntToStr(list.IndexOf(5)));
  if list.BinarySearch(5, loc) then
    ListBox1.Items.Add('Search(5): ' + IntToStr(loc));
end;

圖表翻譯:

此範例展示瞭如何使用 IList<T> 建立一個整數列表,新增元素,插入特定位置的元素,對列表進行排序,以及查詢特定元素的位置。