(*
* Copyright (c) 2008-2011, Ciobanu Alexandru
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of the <organization> nor the
*       names of its contributors may be used to endorse or promote products
*       derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*)
unit Collections.Base;
interface
uses
  SysUtils,
  Rtti,
  Collections.Dynamic,
  Generics.Collections,
  Generics.Defaults;
{$REGION 'Base Collection Interfaces'}
type
  ///  <summary>Base interface describing all enumerators in this package.</summary>
  ///  <remarks><see cref="Collections.Base|IEnumerator<T>">Collections.Base.IEnumerator<T></see> is implemented by
  ///  all enumerator objects in this package.</remarks>
  IEnumerator<T> = interface
    ///  <summary>Returns the current element of the enumerated collection.</summary>
    ///  <remarks><see cref="Collections.Base|IEnumerator<T>.GetCurrent">Collections.Base.IEnumerator<T>.GetCurrent</see> is the
    ///  getter method for the <see cref="Collections.Base|IEnumerator<T>.Current">Collections.Base.IEnumerator<T>.Current</see>
    ///  property. Use the property to obtain the element instead.</remarks>
    ///  <returns>The current element of the enumerated collection.</returns>
    function GetCurrent(): T;
    ///  <summary>Moves the enumerator to the next element of the collection.</summary>
    ///  <remarks><see cref="Collections.Base|IEnumerator<T>.MoveNext">Collections.Base.IEnumerator<T>.MoveNext</see> is usually
    ///  called by compiler-generated code. Its purpose is to move the "pointer" to the next element in the collection
    ///  (if there are elements left). Also note that many enumerator implementations may throw various exceptions if the
    ///  enumerated collections were changed in the meantime.</remarks>
    ///  <returns><c>True</c> if the enumerator successfully selected the next element; <c>False</c> if there are
    ///  no more elements to be enumerated.</returns>
    function MoveNext(): Boolean;
    ///  <summary>Returns the current element of the traversed collection.</summary>
    ///  <remarks><see cref="Collections.Base|IEnumerator<T>.Current">Collections.Base.IEnumerator<T>.Current</see> can only return a
    ///  valid element if <see cref="Collections.Base|IEnumerator<T>.MoveNext">Collections.Base.IEnumerator<T>.MoveNext</see> was
    ///  priorly called and returned <c>True</c>; otherwise the behavior of this property is undefined. Note that many enumerator implementations
    ///  may throw exceptions if the collection was changed in the meantime.
    ///  </remarks>
    ///  <returns>The current element of the enumerator collection.</returns>
    property Current: T read GetCurrent;
  end;
  ///  <summary>Base interface describing all enumerable collections in this package.</summary>
  ///  <remarks><see cref="Collections.Base|IEnumerable<T>">Collections.Base.IEnumerable<T></see> is implemented by all
  ///  enumerable collections in this package.</remarks>
  IEnumerable<T> = interface
    ///  <summary>Returns a <see cref="Collections.Base|IEnumerator<T>">Collections.Base.IEnumerator<T></see> interface that is used
    ///  to enumerate the collection.</summary>
    ///  <remarks><see cref="Collections.Base|IEnumerable<T>.MoveNext">Collections.Base.IEnumerable<T>.MoveNext</see> is usually
    ///  called by compiler-generated code. Its purpose is to create an enumerator object that is used to actually traverse
    ///  the collections.
    ///  Note that many collections generate enumerators that depend on the state of the collection. If the collection is changed
    ///  after the <see cref="Collections.Base|IEnumerator<T>">Collections.Base.IEnumerator<T></see> had been obtained,
    ///  <see cref="Collections.Base|ECollectionChangedException">Collections.Base.ECollectionChangedException</see> is thrown.</remarks>
    ///  <returns>The <see cref="Collections.Base|IEnumerator<T>">Collections.Base.IEnumerator<T></see> interface.</returns>
    function GetEnumerator(): IEnumerator<T>;
  end;
  ///  <summary>A special record designed to hold both a comparer and an equality
  ///  comparer. All collections require this type in order to function properly.</summary>
  ///  <remarks>The collection provided in this package provides extended functionality (Enex), which
  ///  implies comparing values in many circumstances, which requires the presence of the comparer.
  ///  Some collections need an additional equality comparer. This type is meant to provide both
  ///  on the need basis.</remarks>
  TRules<T> = record
  private
    FComparer: IComparer<T>;
    FEqComparer: IEqualityComparer<T>;
  public
    ///  <summary>Initializes a rule set with the given comparers.</summary>
    ///  <param name="AComparer">The comparer.</param>
    ///  <param name="AEqualityComparer">The equality comparer.</param>
    ///  <returns>A rule set initialized with the provided comparers.</returns>
    ///  <exception cref="SysUtils|EArgumentNilException"> if <paramref name="AComparer"/> is <c>nil</c>.</exception>
    ///  <exception cref="SysUtils|EArgumentNilException"> if <paramref name="AEqualityComparer"/> is <c>nil</c>.</exception>
    class function Create(const AComparer: IComparer<T>; const AEqualityComparer: IEqualityComparer<T>): TRules<T>; static;
    ///  <summary>Initializes a rule set with a given custom comparer.</summary>
    ///  <param name="AComparer">The custom comparer.</param>
    ///  <returns>A rule set initialized with the custom comparer.</returns>
    ///  <exception cref="SysUtils|EArgumentNilException"> if <paramref name="AComparer"/> is <c>nil</c>.</exception>
    class function Custom(const AComparer: TCustomComparer<T>): TRules<T>; static;
    ///  <summary>Initializes a rule set using default comparers.</summary>
    ///  <returns>A rule set initialized with the default comparers.</returns>
    class function Default: TRules<T>; static;
  end;
  ///  <summary>Base interface inherited by all specific collection interfaces.</summary>
  ///  <remarks>This interface defines a set of traits common to all collections implemented in this package.</remarks>
  ICollection<T> = interface(IEnumerable<T>)
    ///  <summary>Returns the number of elements in the collection.</summary>
    ///  <returns>A positive value specifying the number of elements in the collection.</returns>
    ///  <remarks>For associative collections such as dictionaries or multimaps, this value represents the
    ///  number of key-value pairs stored in the collection. A call to this method can be costly because some
    ///  collections cannot detect the number of stored elements directly, resorting to enumerating themselves.</remarks>
    function GetCount(): NativeInt;
    ///  <summary>Checks whether the collection is empty.</summary>
    ///  <returns><c>True</c> if the collection is empty; <c>False</c> otherwise.</returns>
    ///  <remarks>This method is the recommended way of detecting if the collection is empty. It is optimized
    ///  in most collections to offer a fast response.</remarks>
    function Empty(): Boolean;
    ///  <summary>Returns the single element stored in the collection.</summary>
    ///  <returns>The element in the collection.</returns>
    ///  <remarks>This method checks whether the collection contains just one element, in which case it is returned.</remarks>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    ///  <exception cref="Collections.Base|ECollectionNotOneException">There is more than one element in the collection.</exception>
    function Single(): T;
    ///  <summary>Returns the single element stored in the collection, or a default value.</summary>
    ///  <param name="ADefault">The default value returned if there is less or more elements in the collection.</param>
    ///  <returns>The element in the collection if the condition is satisfied; <paramref name="ADefault"/> is returned otherwise.</returns>
    ///  <remarks>This method checks whether the collection contains just one element, in which case it is returned. Otherwise
    ///  the value in <paramref name="ADefault"/> is returned.</remarks>
    function SingleOrDefault(const ADefault: T): T;
    ///  <summary>Copies the values stored in the collection to a given array.</summary>
    ///  <param name="AArray">An array where to copy the contents of the collection.</param>
    ///  <remarks>This method assumes that <paramref name="AArray"/> has enough space to hold the contents of the collection.</remarks>
    ///  <exception cref="Collections.Base|EArgumentOutOfSpaceException">The array is not long enough.</exception>
    procedure CopyTo(var AArray: array of T); overload;
    ///  <summary>Copies the values stored in the collection to a given array.</summary>
    ///  <param name="AArray">An array where to copy the contents of the collection.</param>
    ///  <param name="AStartIndex">The index into the array at which the copying begins.</param>
    ///  <remarks>This method assumes that <paramref name="AArray"/> has enough space to hold the contents of the collection.</remarks>
    ///  <exception cref="SysUtils|EArgumentOutOfRangeException"><paramref name="AStartIndex"/> is out of bounds.</exception>
    ///  <exception cref="Collections.Base|EArgumentOutOfSpaceException">The array is not long enough.</exception>
    procedure CopyTo(var AArray: array of T; const AStartIndex: NativeInt); overload;
    ///  <summary>Creates a new Delphi array with the contents of the collection.</summary>
    ///  <remarks>The length of the new array is equal to the value of the <c>Count</c> property.</remarks>
    function ToArray(): TArray<T>;
    ///  <summary>Specifies the number of elements in the collection.</summary>
    ///  <returns>A positive value specifying the number of elements in the collection.</returns>
    ///  <remarks>For associative collections such as dictionaries or multimaps, this value represents the
    ///  number of key-value pairs stored in the collection. Accesing this property can be costly because some
    ///  collections cannot detect the number of stored elements directly, resorting to enumerating themselves.</remarks>
    property Count: NativeInt read GetCount;
  end;
  { Pre-declarations }
  IList<T> = interface;
  ISet<T> = interface;
  IDictionary<TKey, TValue> = interface;
  IEnexCollection<T> = interface;
  IEnexGroupingCollection<TKey, T> = interface;
  ///  <summary>Offers an extended set of Enex operations.</summary>
  ///  <remarks>This type is exposed by Enex collections, and serves simply as a bridge between the interfaces
  ///  and some advanced operations that require parameterized methods. For example, expressions such as
  ///  <c>List.Op.Select<Integer></c> are based on this type.</remarks>
  TEnexExtOps<T> = record
  private
    FRules: TRules<T>;
    FInstance: Pointer;
    FKeepAlive: IInterface;
  public
    ///  <summary>Represents a "select" operation.</summary>
    ///  <param name="ASelector">A selector method invoked for each element in the collection.</param>
    ///  <param name="ARules">A rule set representing the elements in the output collection.</param>
    ///  <returns>A new collection containing the selected values.</returns>
    ///  <remarks>This method is used when it is required to select values related to the ones in the operated collection.
    ///  For example, you can select a collection of integers where each integer is a field of a class in the original collection.</remarks>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="ASelector"/> is <c>nil</c>.</exception>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="ARules"/> is <c>nil</c>.</exception>
    function Select<TOut>(const ASelector: TFunc<T, TOut>; const ARules: TRules<TOut>): IEnexCollection<TOut>; overload;
    ///  <summary>Represents a "select" operation.</summary>
    ///  <param name="ASelector">A selector method invoked for each element in the collection.</param>
    ///  <returns>A new collection containing the selected values.</returns>
    ///  <remarks>This method is used when it is required to select values related to the ones in the operated collection.
    ///  For example, you can select a collection of integers where each integer is a field of a class in the original collection.</remarks>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="ASelector"/> is <c>nil</c>.</exception>
    function Select<TOut>(const ASelector: TFunc<T, TOut>): IEnexCollection<TOut>; overload;
    ///  <summary>Represents a "select" operation.</summary>
    ///  <param name="AMemberName">A record or class field/property name that will be selected.</param>
    ///  <returns>A new collection containing the selected values.</returns>
    ///  <remarks>This method will only work for classes and record types!</remarks>
    ///  <exception cref="Generics.Collections|ENotSupportedException"><paramref name="AMemberName"/> is not a real member of record or class.</exception>
    ///  <exception cref="Generics.Collections|ENotSupportedException">The collection's elements are not objects ore records.</exception>
    function Select<TOut>(const AMemberName: string): IEnexCollection<TOut>; overload;
    ///  <summary>Represents a "select" operation.</summary>
    ///  <param name="AMemberName">A record or class field/property name that will be selected.</param>
    ///  <returns>A new collection containing the selected values represented as Rtti <c>TValue</c>s.</returns>
    ///  <remarks>This method will only work for classes and record types!</remarks>
    ///  <exception cref="Generics.Collections|ENotSupportedException"><paramref name="AMemberName"/> is not a real member of record or class.</exception>
    ///  <exception cref="Generics.Collections|ENotSupportedException">The collection's elements are not objects ore records.</exception>
    function Select(const AMemberName: string): IEnexCollection<TAny>; overload;
    ///  <summary>Represents a "select" operation.</summary>
    ///  <param name="AMemberNames">A record or class field/property names that will be selected.</param>
    ///  <returns>A new collection containing the selected values represented as a view.</returns>
    ///  <remarks>This method will only work for classes and record types! The resulting view contains the selected members.</remarks>
    ///  <exception cref="Generics.Collections|ENotSupportedException"><paramref name="AMemberName"/> is not a real member of record or class.</exception>
    ///  <exception cref="Generics.Collections|ENotSupportedException">The collection's elements are not objects ore records.</exception>
    function Select(const AMemberNames: array of string): IEnexCollection<TView>; overload;
    ///  <summary>Represents a "where, select object" operation.</summary>
    ///  <returns>A new collection containing the selected values.</returns>
    ///  <remarks>This method can be used on a collection containing objects. The operation involves two steps,
    ///  where and select. First, each object is checked to be derived from <c>TOut</c>. If that is true, it is then
    ///  cast to <c>TOut</c>. The result of the operation is a new collection that contains only the objects of a given
    ///  class. For example, <c>AList.Op.Select<TMyObject></c> results in a new collection that only contains
    ///  "TMyObject" instances.</remarks>
    ///  <exception cref="Generics.Collections|ENotSupportedException">The collection's elements are not objects.</exception>
    function Select<TOut: class>(): IEnexCollection<TOut>; overload;
    ///  <summary>Groups all elements in the collection by a given key.</summary>
    ///  <param name="ASelector">The selector function. Returns the key (based on each collection element) that serves for grouping purposes.</param>
    ///  <returns>A collection of grouping collections.</returns>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="ASelector"/> is <c>nil</c>.</exception>
    ///  <remarks>This operation will call <paramref name="ASelector"/> for each element in the collection and retrieve a "key". Using this key,
    ///  the elements are grouped into new collections called groupings. The result of this operation is a collection of groupings. Each grouping
    ///  contains the elements from the original collection that have the same group and a key (which is the group value used).</remarks>
    function GroupBy<TKey>(const ASelector: TFunc<T, TKey>): IEnexCollection<IEnexGroupingCollection<TKey, T>>; overload;
  end;
  ///  <summary>Base Enex (Extended enumerable) interface inherited by all specific collection interfaces.</summary>
  ///  <remarks>This interface defines a set of traits common to all collections implemented in this package. It also introduces
  ///  a large set of extended operations that can be performed on any collection that supports enumerability.</remarks>
  IEnexCollection<T> = interface(ICollection<T>)
    ///  <summary>Checks whether the elements in this collection are equal to the elements in another collection.</summary>
    ///  <param name="ACollection">The collection to compare to.</param>
    ///  <returns><c>True</c> if the collections are equal; <c>False</c> if the collections are different.</returns>
    ///  <remarks>This method checks that each element at position X in this collection is equal to an element at position X in
    ///  the provided collection. If the number of elements in both collections is different, then the collections are considered different.
    ///  Note that comparison of element is done using the rule set used by this collection. This means that comparing this collection
    ///  to another one might yield a different result than comparing the other collection to this one.</remarks>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="ACollection"/> is <c>nil</c>.</exception>
    function EqualsTo(const ACollection: IEnumerable<T>): Boolean;
    ///  <summary>Creates a new list containing the elements of this collection.</summary>
    ///  <returns>A list containing the elements copied from this collection.</returns>
    ///  <remarks>This method also copies the rule set of this collection. Be careful if the rule set
    ///  performs cleanup on the elements.</remarks>
    function ToList(): IList<T>;
    ///  <summary>Creates a new set containing the elements of this collection.</summary>
    ///  <returns>A set containing the elements copied from this collection.</returns>
    ///  <remarks>This method also copies the rule set of this collection. Be careful if the rule set
    ///  performs cleanup on the elements.</remarks>
    function ToSet(): ISet<T>;
    ///  <summary>Returns the biggest element.</summary>
    ///  <returns>An element from the collection considered to have the biggest value.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    function Max(): T;
    ///  <summary>Returns the smallest element.</summary>
    ///  <returns>An element from the collection considered to have the smallest value.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    function Min(): T;
    ///  <summary>Returns the first element.</summary>
    ///  <returns>The first element in the collection.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    function First(): T;
    ///  <summary>Returns the first element or a default if the collection is empty.</summary>
    ///  <param name="ADefault">The default value returned if the collection is empty.</param>
    ///  <returns>The first element in the collection if the collection is not empty; otherwise <paramref name="ADefault"/> is returned.</returns>
    function FirstOrDefault(const ADefault: T): T;
    ///  <summary>Returns the first element that satisfies the given predicate.</summary>
    ///  <param name="APredicate">The predicate to use.</param>
    ///  <returns>The first element that satisfies the given predicate.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    ///  <exception cref="Collections.Base|ECollectionFilteredEmptyException">No elements satisfy the predicate.</exception>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="APredicate"/> is <c>nil</c>.</exception>
    function FirstWhere(const APredicate: TFunc<T, Boolean>): T;
    ///  <summary>Returns the first element that satisfies the given predicate or a default value.</summary>
    ///  <param name="APredicate">The predicate to use.</param>
    ///  <param name="ADefault">The default value.</param>
    ///  <returns>The first element that satisfies the given predicate; <paramref name="ADefault"/> otherwise.</returns>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="APredicate"/> is <c>nil</c>.</exception>
    function FirstWhereOrDefault(const APredicate: TFunc<T, Boolean>; const ADefault: T): T;
    ///  <summary>Returns the first element that does not satisfy the given predicate.</summary>
    ///  <param name="APredicate">The predicate to use.</param>
    ///  <returns>The first element that does not satisfy the given predicate.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    ///  <exception cref="Collections.Base|ECollectionFilteredEmptyException">No elements that do not satisfy the predicate.</exception>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="APredicate"/> is <c>nil</c>.</exception>
    function FirstWhereNot(const APredicate: TFunc<T, Boolean>): T;
    ///  <summary>Returns the first element that does not satisfy the given predicate or a default value.</summary>
    ///  <param name="APredicate">The predicate to use.</param>
    ///  <param name="ADefault">The default value.</param>
    ///  <returns>The first element that does not satisfy the given predicate; <paramref name="ADefault"/> otherwise.</returns>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="APredicate"/> is <c>nil</c>.</exception>
    function FirstWhereNotOrDefault(const APredicate: TFunc<T, Boolean>; const ADefault: T): T;
    ///  <summary>Returns the first element lower than a given value.</summary>
    ///  <param name="ABound">The value to compare against.</param>
    ///  <returns>The first element that satisfies the given condition.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    ///  <exception cref="Collections.Base|ECollectionFilteredEmptyException">No elements satisfy the condition.</exception>
    function FirstWhereLower(const ABound: T): T;
    ///  <summary>Returns the first element lower than a given value or a default.</summary>
    ///  <param name="ABound">The value to compare against.</param>
    ///  <param name="ADefault">The default value.</param>
    ///  <returns>The first element that satisfies the given condition; <paramref name="ADefault"/> otherwise.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    ///  <exception cref="Collections.Base|ECollectionFilteredEmptyException">No elements satisfy the condition.</exception>
    function FirstWhereLowerOrDefault(const ABound: T; const ADefault: T): T;
    ///  <summary>Returns the first element lower than or equal to a given value.</summary>
    ///  <param name="ABound">The value to compare against.</param>
    ///  <returns>The first element that satisfies the given condition.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    ///  <exception cref="Collections.Base|ECollectionFilteredEmptyException">No elements satisfy the condition.</exception>
    function FirstWhereLowerOrEqual(const ABound: T): T;
    ///  <summary>Returns the first element lower than or equal to a given value or a default.</summary>
    ///  <param name="ABound">The value to compare against.</param>
    ///  <param name="ADefault">The default value.</param>
    ///  <returns>The first element that satisfies the given condition; <paramref name="ADefault"/> otherwise.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    ///  <exception cref="Collections.Base|ECollectionFilteredEmptyException">No elements satisfy the condition.</exception>
    function FirstWhereLowerOrEqualOrDefault(const ABound: T; const ADefault: T): T;
    ///  <summary>Returns the first element greater than a given value.</summary>
    ///  <param name="ABound">The value to compare against.</param>
    ///  <returns>The first element that satisfies the given condition.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    ///  <exception cref="Collections.Base|ECollectionFilteredEmptyException">No elements satisfy the condition.</exception>
    function FirstWhereGreater(const ABound: T): T;
    ///  <summary>Returns the first element greater than a given value or a default.</summary>
    ///  <param name="ABound">The value to compare against.</param>
    ///  <param name="ADefault">The default value.</param>
    ///  <returns>The first element that satisfies the given condition; <paramref name="ADefault"/> otherwise.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    ///  <exception cref="Collections.Base|ECollectionFilteredEmptyException">No elements satisfy the condition.</exception>
    function FirstWhereGreaterOrDefault(const ABound: T; const ADefault: T): T;
    ///  <summary>Returns the first element greater than or equal to a given value.</summary>
    ///  <param name="ABound">The value to compare against.</param>
    ///  <returns>The first element that satisfies the given condition.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    ///  <exception cref="Collections.Base|ECollectionFilteredEmptyException">No elements satisfy the condition.</exception>
    function FirstWhereGreaterOrEqual(const ABound: T): T;
    ///  <summary>Returns the first element greater than or equal to a given value or a default.</summary>
    ///  <param name="ABound">The value to compare against.</param>
    ///  <param name="ADefault">The default value.</param>
    ///  <returns>The first element that satisfies the given condition; <paramref name="ADefault"/> otherwise.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    ///  <exception cref="Collections.Base|ECollectionFilteredEmptyException">No elements satisfy the condition.</exception>
    function FirstWhereGreaterOrEqualOrDefault(const ABound: T; const ADefault: T): T;
    ///  <summary>Returns the first element situated within the given bounds.</summary>
    ///  <param name="ALower">The lower bound.</param>
    ///  <param name="AHigher">The higher bound.</param>
    ///  <returns>The first element that satisfies the given condition.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    ///  <exception cref="Collections.Base|ECollectionFilteredEmptyException">No elements satisfy the condition.</exception>
    function FirstWhereBetween(const ALower, AHigher: T): T;
    ///  <summary>Returns the first element situated within the given bounds or a default.</summary>
    ///  <param name="ALower">The lower bound.</param>
    ///  <param name="AHigher">The higher bound.</param>
    ///  <param name="ADefault">The default value.</param>
    ///  <returns>The first element that satisfies the given condition; <paramref name="ADefault"/> otherwise.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    ///  <exception cref="Collections.Base|ECollectionFilteredEmptyException">No elements satisfy the condition.</exception>
    function FirstWhereBetweenOrDefault(const ALower, AHigher: T; const ADefault: T): T;
    ///  <summary>Returns the last element.</summary>
    ///  <returns>The last element in the collection.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    function Last(): T;
    ///  <summary>Returns the last element or a default if the collection is empty.</summary>
    ///  <param name="ADefault">The default value returned if the collection is empty.</param>
    ///  <returns>The last element in the collection if the collection is not empty; otherwise <paramref name="ADefault"/> is returned.</returns>
    function LastOrDefault(const ADefault: T): T;
    ///  <summary>Aggregates a value based on the collection's elements.</summary>
    ///  <param name="AAggregator">The aggregator method.</param>
    ///  <returns>A value that contains the collection's aggregated value.</returns>
    ///  <remarks>This method returns the first element if the collection only has one element. Otherwise,
    ///  <paramref name="AAggregator"/> is invoked for each two elements (first and second; then the result of the first two
    ///  and the third, and so on). The simplest example of aggregation is the "sum" operation where you can obtain the sum of all
    ///  elements in the value.</remarks>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="AAggregator"/> is <c>nil</c>.</exception>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    function Aggregate(const AAggregator: TFunc<T, T, T>): T;
    ///  <summary>Aggregates a value based on the collection's elements.</summary>
    ///  <param name="AAggregator">The aggregator method.</param>
    ///  <param name="ADefault">The default value returned if the collection is empty.</param>
    ///  <returns>A value that contains the collection's aggregated value. If the collection is empty, <paramref name="ADefault"/> is returned.</returns>
    ///  <remarks>This method returns the first element if the collection only has one element. Otherwise,
    ///  <paramref name="AAggregator"/> is invoked for each two elements (first and second; then the result of the first two
    ///  and the third, and so on). The simplest example of aggregation is the "sum" operation where you can obtain the sum of all
    ///  elements in the value.</remarks>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="AAggregator"/> is <c>nil</c>.</exception>
    function AggregateOrDefault(const AAggregator: TFunc<T, T, T>; const ADefault: T): T;
    ///  <summary>Returns the element at a given position.</summary>
    ///  <param name="AIndex">The index from which to return the element.</param>
    ///  <returns>The element at the specified position.</returns>
    ///  <remarks>This method is slow for collections that cannot reference their elements by indexes; for example: linked lists</remarks>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    ///  <exception cref="SysUtils|EArgumentOutOfRangeException"><paramref name="AIndex"/> is out of bounds.</exception>
    function ElementAt(const AIndex: NativeInt): T;
    ///  <summary>Returns the element at a given position.</summary>
    ///  <param name="AIndex">The index from which to return the element.</param>
    ///  <param name="ADefault">The default value returned if the collection is empty.</param>
    ///  <returns>The element at the specified position if the collection is not empty and the position is not out of bounds; otherwise
    ///  the value of <paramref name="ADefault"/> is returned.</returns>
    ///  <remarks>This method is slow for collections that cannot reference their elements by indexes; for example: linked lists</remarks>
    function ElementAtOrDefault(const AIndex: NativeInt; const ADefault: T): T;
    ///  <summary>Check whether at least one element in the collection satisfies a given predicate.</summary>
    ///  <param name="APredicate">The predicate to check for each element.</param>
    ///  <returns><c>True</c> if the at least one element satisfies a given predicate; <c>False</c> otherwise.</returns>
    ///  <remarks>This method traverses the whole collection and checks the value of the predicate for each element. This method
    ///  stops on the first element for which the predicate returns <c>True</c>. The logical equivalent of this operation is "OR".</remarks>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="APredicate"/> is <c>nil</c>.</exception>
    function Any(const APredicate: TFunc<T, Boolean>): Boolean;
    ///  <summary>Checks that all elements in the collection satisfies a given predicate.</summary>
    ///  <param name="APredicate">The predicate to check for each element.</param>
    ///  <returns><c>True</c> if all elements satisfy a given predicate; <c>False</c> otherwise.</returns>
    ///  <remarks>This method traverses the whole collection and checks the value of the predicate for each element. This method
    ///  stops on the first element for which the predicate returns <c>False</c>. The logical equivalent of this operation is "AND".</remarks>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="APredicate"/> is <c>nil</c>.</exception>
    function All(const APredicate: TFunc<T, Boolean>): Boolean;
    ///  <summary>Selects only the elements that satisfy a given rule.</summary>
    ///  <param name="APredicate">The predicate that represents the rule.</param>
    ///  <returns>A new collection that contains only the elements that satisfy the given rule.</returns>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="APredicate"/> is <c>nil</c>.</exception>
    function Where(const APredicate: TFunc<T, Boolean>): IEnexCollection<T>;
    ///  <summary>Selects only the elements that do not satisfy a given rule.</summary>
    ///  <param name="APredicate">The predicate that represents the rule.</param>
    ///  <returns>A new collection that contains only the elements that do not satisfy the given rule.</returns>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="APredicate"/> is <c>nil</c>.</exception>
    function WhereNot(const APredicate: TFunc<T, Boolean>): IEnexCollection<T>;
    ///  <summary>Selects only the elements that are less than a given value.</summary>
    ///  <param name="ABound">The element to compare against.</param>
    ///  <returns>A new collection that contains only the elements that satisfy the relationship.</returns>
    function WhereLower(const ABound: T): IEnexCollection<T>;
    ///  <summary>Selects only the elements that are less than or equal to a given value.</summary>
    ///  <param name="ABound">The element to compare against.</param>
    ///  <returns>A new collection that contains only the elements that satisfy the relationship.</returns>
    function WhereLowerOrEqual(const ABound: T): IEnexCollection<T>;
    ///  <summary>Selects only the elements that are greater than a given value.</summary>
    ///  <param name="ABound">The element to compare against.</param>
    ///  <returns>A new collection that contains only the elements that satisfy the relationship.</returns>
    function WhereGreater(const ABound: T): IEnexCollection<T>;
    ///  <summary>Selects only the elements that are greater than or equal to a given value.</summary>
    ///  <param name="ABound">The element to compare against.</param>
    ///  <returns>A new collection that contains only the elements that satisfy the relationship.</returns>
    function WhereGreaterOrEqual(const ABound: T): IEnexCollection<T>;
    ///  <summary>Selects only the elements whose values are contained whithin a given interval.</summary>
    ///  <param name="ALower">The lower bound.</param>
    ///  <param name="AHigher">The upper bound.</param>
    ///  <returns>A new collection that contains only the elements that satisfy the relationship.</returns>
    ///  <remarks>The elements that are equal to the lower or upper bounds, are also included.</remarks>
    function WhereBetween(const ALower, AHigher: T): IEnexCollection<T>;
    ///  <summary>Selects all the elements from the collection excluding duplicates.</summary>
    ///  <returns>A new collection that contains the distinct elements.</returns>
    function Distinct(): IEnexCollection<T>;
    ///  <summary>Returns a new ordered collection that contains the elements from this collection.</summary>
    ///  <param name="AAscending">Specifies whether the elements are ordered ascending or descending.</param>
    ///  <returns>A new ordered collection.</returns>
    function Ordered(const AAscending: Boolean = true): IEnexCollection<T>; overload;
    ///  <summary>Returns a new ordered collection that contains the elements from this collection.</summary>
    ///  <param name="ASortProc">The comparison method.</param>
    ///  <returns>A new ordered collection.</returns>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="ASortProc"/> is <c>nil</c>.</exception>
    function Ordered(const ASortProc: TComparison<T>): IEnexCollection<T>; overload;
    ///  <summary>Revereses the contents of the collection.</summary>
    ///  <returns>A new collection that contains the elements from this collection but in reverse order.</returns>
    function Reversed(): IEnexCollection<T>;
    ///  <summary>Concatenates this collection with another collection.</summary>
    ///  <param name="ACollection">A collection to concatenate.</param>
    ///  <returns>A new collection that contains the elements from this collection followed by elements
    ///  from the given collection.</returns>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="ACollection"/> is <c>nil</c>.</exception>
    function Concat(const ACollection: IEnexCollection<T>): IEnexCollection<T>;
    ///  <summary>Creates a new collection that contains the elements from both collections taken a single time.</summary>
    ///  <param name="ACollection">The collection to unify with.</param>
    ///  <returns>A new collection that contains the elements from this collection followed by elements
    ///  from the given collection except the elements that already are present in this collection. This operation can be seen as
    ///  a "concat" operation followed by a "distinct" operation. </returns>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="ACollection"/> is <c>nil</c>.</exception>
    function Union(const ACollection: IEnexCollection<T>): IEnexCollection<T>;
    ///  <summary>Creates a new collection that contains the elements from this collection minus the ones in the given collection.</summary>
    ///  <param name="ACollection">The collection to exclude.</param>
    ///  <returns>A new collection that contains the elements from this collection minus the those elements that are common between
    ///  this and the given collection.</returns>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="ACollection"/> is <c>nil</c>.</exception>
    function Exclude(const ACollection: IEnexCollection<T>): IEnexCollection<T>;
    ///  <summary>Creates a new collection that contains the elements that are present in both collections.</summary>
    ///  <param name="ACollection">The collection to interset with.</param>
    ///  <returns>A new collection that contains the elements that are common to both collections.</returns>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="ACollection"/> is <c>nil</c>.</exception>
    function Intersect(const ACollection: IEnexCollection<T>): IEnexCollection<T>;
    ///  <summary>Select the elements that whose indexes are located in the given range.</summary>
    ///  <param name="AStart">The lower bound.</param>
    ///  <param name="AEnd">The upper bound.</param>
    ///  <returns>A new collection that contains the elements whose indexes in this collection are locate between <paramref name="AStart"/>
    ///  and <paramref name="AEnd"/>. Note that this method does not check the indexes. This means that a bad combination of parameters will
    ///  simply result in an empty or incorrect result.</returns>
    function Range(const AStart, AEnd: NativeInt): IEnexCollection<T>;
    ///  <summary>Selects only a given amount of elements.</summary>
    ///  <param name="ACount">The number of elements to select.</param>
    ///  <returns>A new collection that contains only the first <paramref name="ACount"/> elements.</returns>
    ///  <exception cref="SysUtils|EArgumentOutOfRangeException"><paramref name="ACount"/> is zero.</exception>
    function Take(const ACount: NativeInt): IEnexCollection<T>;
    ///  <summary>Selects all the elements from the collection while a given rule is satisfied.</summary>
    ///  <param name="APredicate">The rule to satisfy.</param>
    ///  <returns>A new collection that contains the selected elements.</returns>
    ///  <remarks>This method selects all elements from the collection while the given rule is satisfied.</remarks>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="APredicate"/> is <c>nil</c>.</exception>
    function TakeWhile(const APredicate: TFunc<T, Boolean>): IEnexCollection<T>;
    ///  <summary>Selects all the elements from the collection while elements are lower than a given value.</summary>
    ///  <param name="ABound">The value to check against.</param>
    ///  <returns>A new collection that contains the selected elements.</returns>
    ///  <remarks>This method selects all elements from the collection while the given rule is satisfied.</remarks>
    function TakeWhileLower(const ABound: T): IEnexCollection<T>;
    ///  <summary>Selects all the elements from the collection while elements are lower than
    ///  or equals to a given value.</summary>
    ///  <param name="ABound">The value to check against.</param>
    ///  <returns>A new collection that contains the selected elements.</returns>
    ///  <remarks>This method selects all elements from the collection while the given rule is satisfied.</remarks>
    function TakeWhileLowerOrEqual(const ABound: T): IEnexCollection<T>;
    ///  <summary>Selects all the elements from the collection while elements are greater than
    ///  a given value.</summary>
    ///  <param name="ABound">The value to check against.</param>
    ///  <returns>A new collection that contains the selected elements.</returns>
    ///  <remarks>This method selects all elements from the collection while the given rule is satisfied.</remarks>
    function TakeWhileGreater(const ABound: T): IEnexCollection<T>;
    ///  <summary>Selects all the elements from the collection while elements are greater than
    ///  or equals to a given value.</summary>
    ///  <param name="ABound">The value to check against.</param>
    ///  <returns>A new collection that contains the selected elements.</returns>
    ///  <remarks>This method selects all elements from the collection while the given rule is satisfied.</remarks>
    function TakeWhileGreaterOrEqual(const ABound: T): IEnexCollection<T>;
    ///  <summary>Selects all the elements from the collection while elements are between a given range of values.</summary>
    ///  <param name="ALower">The lower bound.</param>
    ///  <param name="AHigher">The higher bound.</param>
    ///  <returns>A new collection that contains the selected elements.</returns>
    ///  <remarks>This method selects all elements from the collection while the given rule is satisfied.</remarks>
    function TakeWhileBetween(const ALower, AHigher: T): IEnexCollection<T>;
    ///  <summary>Skips a given amount of elements.</summary>
    ///  <param name="ACount">The number of elements to skip.</param>
    ///  <returns>A new collection that contains the elements that were not skipped.</returns>
    ///  <exception cref="SysUtils|EArgumentOutOfRangeException"><paramref name="ACount"/> is zero.</exception>
    function Skip(const ACount: NativeInt): IEnexCollection<T>;
    ///  <summary>Skips all the elements from the collection while a given rule is satisfied.</summary>
    ///  <param name="APredicate">The rule to satisfy.</param>
    ///  <returns>A new collection that contains the elements that were not skipped.</returns>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="APredicate"/> is <c>nil</c>.</exception>
    function SkipWhile(const APredicate: TFunc<T, Boolean>): IEnexCollection<T>;
    ///  <summary>Skips all the elements from the collection while elements are lower than a given value.</summary>
    ///  <param name="ABound">The value to check.</param>
    ///  <returns>A new collection that contains the elements that were not skipped.</returns>
    function SkipWhileLower(const ABound: T): IEnexCollection<T>;
    ///  <summary>Skips all the elements from the collection while elements are lower than or equal to a given value.</summary>
    ///  <param name="ABound">The value to check.</param>
    ///  <returns>A new collection that contains the elements that were not skipped.</returns>
    function SkipWhileLowerOrEqual(const ABound: T): IEnexCollection<T>;
    ///  <summary>Skips all the elements from the collection while elements are greater than a given value.</summary>
    ///  <param name="ABound">The value to check.</param>
    ///  <returns>A new collection that contains the elements that were not skipped.</returns>
    function SkipWhileGreater(const ABound: T): IEnexCollection<T>;
    ///  <summary>Skips all the elements from the collection while elements are greater than or equal to a given value.</summary>
    ///  <param name="ABound">The value to check.</param>
    ///  <returns>A new collection that contains the elements that were not skipped.</returns>
    function SkipWhileGreaterOrEqual(const ABound: T): IEnexCollection<T>;
    ///  <summary>Skips all the elements from the collection while elements are between a given range of values.</summary>
    ///  <param name="ALower">The lower bound.</param>
    ///  <param name="AHigher">The higher bound.</param>
    ///  <returns>A new collection that contains the elements that were not skipped.</returns>
    function SkipWhileBetween(const ALower, AHigher: T): IEnexCollection<T>;
    ///  <summary>Exposes a type that provides extended Enex operations such as "select".</summary>
    ///  <returns>A record that exposes more Enex operations that otherwise would be impossible.</returns>
    function Op: TEnexExtOps<T>;
  end;
  ///  <summary>Enex collection that is presumed to be grouped by a certain key.</summary>
  IEnexGroupingCollection<TKey, T> = interface(IEnexCollection<T>)
    ///  <summary>Returns the key under which all elements in this collection are grouped.</summary>
    ///  <returns>The key of this grouping.</returns>
    function GetKey(): TKey;
    ///  <summary>Returns the key under which all elements in this collection are grouped.</summary>
    ///  <returns>The key of this grouping.</returns>
    property Key: TKey read GetKey;
  end;
  ///  <summary>The Enex interface implemented in collections that allow indexed element access.</summary>
  ///  <remarks>This interface is inherited by other more specific interfaces such as lists. Indexed collections
  ///  allow their elements to be accesed given a numeric index.</remarks>
  IEnexIndexedCollection<T> = interface(IEnexCollection<T>)
    ///  <summary>Returns the item from a given index.</summary>
    ///  <param name="AIndex">The index in the collection.</param>
    ///  <returns>The element at the specified position.</returns>
    ///  <remarks>This method is similar to <c>ElementAt</c>. The only difference is that this method is guaranteed
    ///  to provide the fastest lookup (normally <c>ElementAt</c> should also use the same method in indexed collections).</remarks>
    ///  <exception cref="SysUtils|EArgumentOutOfRangeException"><paramref name="AIndex"/> is out of bounds.</exception>
    function GetItem(const AIndex: NativeInt): T;
    ///  <summary>Returns the item from a given index.</summary>
    ///  <param name="AIndex">The index in the collection.</param>
    ///  <returns>The element at the specified position.</returns>
    ///  <exception cref="SysUtils|EArgumentOutOfRangeException"><paramref name="AIndex"/> is out of bounds.</exception>
    property Items[const AIndex: NativeInt]: T read GetItem; default;
  end;
  ///  <summary>Base Enex (Extended enumerable) interface inherited by all specific associative collection interfaces.</summary>
  ///  <remarks>This interface defines a set of traits common to all associative collections implemented in this package. It also introduces
  ///  a large se of extended operations that can pe performed on any collection that supports enumerability.</remarks>
  IEnexAssociativeCollection<TKey, TValue> = interface(ICollection<TPair<TKey, TValue>>)
    ///  <summary>Creates a new dictionary containing the elements of this collection.</summary>
    ///  <returns>A dictionary containing the elements copied from this collection.</returns>
    ///  <remarks>This method also copies the rule sets of this collection. Be careful if the rule set
    ///  performs cleanup on the elements.</remarks>
    ///  <exception cref="Collections.Base|EDuplicateKeyException">The collection contains more than
    ///  one key-value pair with the same key.</exception>
    function ToDictionary(): IDictionary<TKey, TValue>;
    ///  <summary>Returns the value associated with the given key.</summary>
    ///  <param name="AKey">The key for which to return the associated value.</param>
    ///  <returns>The value associated with the given key.</returns>
    ///  <exception cref="Collections.Base|EKeyNotFoundException">No such key in the collection.</exception>
    function ValueForKey(const AKey: TKey): TValue;
    ///  <summary>Checks whether the collection contains a given key-value pair.</summary>
    ///  <param name="AKey">The key part of the pair.</param>
    ///  <param name="AValue">The value part of the pair.</param>
    ///  <returns><c>True</c> if the given key-value pair exists; <c>False</c> otherwise.</returns>
    function KeyHasValue(const AKey: TKey; const AValue: TValue): Boolean;
    ///  <summary>Returns the biggest key.</summary>
    ///  <returns>The biggest key stored in the collection.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    function MaxKey(): TKey;
    ///  <summary>Returns the smallest key.</summary>
    ///  <returns>The smallest key stored in the collection.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    function MinKey(): TKey;
    ///  <summary>Returns the biggest value.</summary>
    ///  <returns>The biggest value stored in the collection.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    function MaxValue(): TValue;
    ///  <summary>Returns the smallest value.</summary>
    ///  <returns>The smallest value stored in the collection.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    function MinValue(): TValue;
    ///  <summary>Returns an Enex collection that contains only the keys.</summary>
    ///  <returns>An Enex collection that contains all the keys stored in the collection.</returns>
    function SelectKeys(): IEnexCollection<TKey>;
    ///  <summary>Returns a Enex collection that contains only the values.</summary>
    ///  <returns>An Enex collection that contains all the values stored in the collection.</returns>
    function SelectValues(): IEnexCollection<TValue>;
    ///  <summary>Specifies the collection that contains only the keys.</summary>
    ///  <returns>An Enex collection that contains all the keys stored in the collection.</returns>
    property Keys: IEnexCollection<TKey> read SelectKeys;
    ///  <summary>Specifies the collection that contains only the values.</summary>
    ///  <returns>An Enex collection that contains all the values stored in the collection.</returns>
    property Values: IEnexCollection<TValue> read SelectValues;
    ///  <summary>Selects all the key-value pairs from the collection excluding the duplicates by key.</summary>
    ///  <returns>A new collection that contains the distinct pairs.</returns>
    function DistinctByKeys(): IEnexAssociativeCollection<TKey, TValue>;
    ///  <summary>Selects all the key-value pairs from the collection excluding the duplicates by value.</summary>
    ///  <returns>A new collection that contains the distinct pairs.</returns>
    function DistinctByValues(): IEnexAssociativeCollection<TKey, TValue>;
    ///  <summary>Checks whether this collection includes the key-value pairs in another collection.</summary>
    ///  <param name="ACollection">The collection to check against.</param>
    ///  <returns><c>True</c> if this collection includes the elements in another; <c>False</c> otherwise.</returns>
    function Includes(const ACollection: IEnumerable<TPair<TKey, TValue>>): Boolean;
    ///  <summary>Selects only the key-value pairs that satisfy a given rule.</summary>
    ///  <param name="APredicate">The predicate that represents the rule.</param>
    ///  <returns>A new collection that contains only the pairs that satisfy the given rule.</returns>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="APredicate"/> is <c>nil</c>.</exception>
    function Where(const APredicate: TFunc<TKey, TValue, Boolean>): IEnexAssociativeCollection<TKey, TValue>;
    ///  <summary>Selects only the key-value pairs that do not satisfy a given rule.</summary>
    ///  <param name="APredicate">The predicate that represents the rule.</param>
    ///  <returns>A new collection that contains only the pairs that do not satisfy the given rule.</returns>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="APredicate"/> is <c>nil</c>.</exception>
    function WhereNot(const APredicate: TFunc<TKey, TValue, Boolean>): IEnexAssociativeCollection<TKey, TValue>;
    ///  <summary>Selects only the key-value pairs whose keys are less than a given value.</summary>
    ///  <param name="ABound">The value to compare against.</param>
    ///  <returns>A new collection that contains only the pairs that satisfy the relationship.</returns>
    function WhereKeyLower(const ABound: TKey): IEnexAssociativeCollection<TKey, TValue>;
    ///  <summary>Selects only the key-value pairs whose keys are less than or equal to a given value.</summary>
    ///  <param name="ABound">The value to compare against.</param>
    ///  <returns>A new collection that contains only the pairs that satisfy the relationship.</returns>
    function WhereKeyLowerOrEqual(const ABound: TKey): IEnexAssociativeCollection<TKey, TValue>;
    ///  <summary>Selects only the key-value pairs whose keys are greater than a given value.</summary>
    ///  <param name="ABound">The value to compare against.</param>
    ///  <returns>A new collection that contains only the pairs that satisfy the relationship.</returns>
    function WhereKeyGreater(const ABound: TKey): IEnexAssociativeCollection<TKey, TValue>;
    ///  <summary>Selects only the key-value pairs whose keys are greater than or equal to a given value.</summary>
    ///  <param name="ABound">The value to compare against.</param>
    ///  <returns>A new collection that contains only the pairs that satisfy the relationship.</returns>
    function WhereKeyGreaterOrEqual(const ABound: TKey): IEnexAssociativeCollection<TKey, TValue>;
    ///  <summary>Selects only the key-value pairs whose keys are are contained whithin a given interval.</summary>
    ///  <param name="ALower">The lower bound.</param>
    ///  <param name="AHigher">The upper bound.</param>
    ///  <returns>A new collection that contains only the pairs that satisfy the relationship.</returns>
    function WhereKeyBetween(const ALower, AHigher: TKey): IEnexAssociativeCollection<TKey, TValue>;
    ///  <summary>Selects only the key-value pairs whose values are less than a given value.</summary>
    ///  <param name="ABound">The value to compare against.</param>
    ///  <returns>A new collection that contains only the pairs that satisfy the relationship.</returns>
    function WhereValueLower(const ABound: TValue): IEnexAssociativeCollection<TKey, TValue>;
    ///  <summary>Selects only the key-value pairs whose values are less than or equal to a given value.</summary>
    ///  <param name="ABound">The value to compare against.</param>
    ///  <returns>A new collection that contains only the pairs that satisfy the relationship.</returns>
    function WhereValueLowerOrEqual(const ABound: TValue): IEnexAssociativeCollection<TKey, TValue>;
    ///  <summary>Selects only the key-value pairs whose values are greater than a given value.</summary>
    ///  <param name="ABound">The value to compare against.</param>
    ///  <returns>A new collection that contains only the pairs that satisfy the relationship.</returns>
    function WhereValueGreater(const ABound: TValue): IEnexAssociativeCollection<TKey, TValue>;
    ///  <summary>Selects only the key-value pairs whose values are greater than or equal to a given value.</summary>
    ///  <param name="ABound">The value to compare against.</param>
    ///  <returns>A new collection that contains only the pairs that satisfy the relationship.</returns>
    function WhereValueGreaterOrEqual(const ABound: TValue): IEnexAssociativeCollection<TKey, TValue>;
    ///  <summary>Selects only the key-value pairs whose values are are contained whithin a given interval.</summary>
    ///  <param name="ALower">The lower bound.</param>
    ///  <param name="AHigher">The upper bound.</param>
    ///  <returns>A new collection that contains only the pairs that satisfy the relationship.</returns>
    function WhereValueBetween(const ALower, AHigher: TValue): IEnexAssociativeCollection<TKey, TValue>;
  end;
  ///  <summary>The Enex interface that defines the behavior of a <c>stack</c>.</summary>
  ///  <remarks>This interface is implemented by all collections that provide the functionality of a <c>stack</c>.</remarks>
  IStack<T> = interface(IEnexCollection<T>)
    ///  <summary>Clears the contents of the stack.</summary>
    procedure Clear();
    ///  <summary>Pushes an element to the top of the stack.</summary>
    ///  <param name="AValue">The value to push.</param>
    procedure Push(const AValue: T);
    ///  <summary>Retrieves the element from the top of the stack.</summary>
    ///  <returns>The value at the top of the stack.</returns>
    ///  <remarks>This method removes the element from the top of the stack.</remarks>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The stack is empty.</exception>
    function Pop(): T;
    ///  <summary>Reads the element from the top of the stack.</summary>
    ///  <returns>The value at the top of the stack.</returns>
    ///  <remarks>This method does not remove the element from the top of the stack. It merely reads it's value.</remarks>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The stack is empty.</exception>
    function Peek(): T;
    ///  <summary>Removes an element from the stack.</summary>
    ///  <param name="AValue">The value to remove. If there is no such element in the stack, nothing happens.</param>
    procedure Remove(const AValue: T);
    ///  <summary>Checks whether the stack contains a given value.</summary>
    ///  <param name="AValue">The value to check.</param>
    ///  <returns><c>True</c> if the value was found in the stack; <c>False</c> otherwise.</returns>
    function Contains(const AValue: T): Boolean;
  end;
  ///  <summary>The Enex interface that defines the behavior of a <c>queue</c>.</summary>
  ///  <remarks>This interface is implemented by all collections that provide the functionality of a <c>queue</c>.</remarks>
  IQueue<T> = interface(IEnexCollection<T>)
    ///  <summary>Clears the contents of the queue.</summary>
    procedure Clear();
    ///  <summary>Appends an element to the top of the queue.</summary>
    ///  <param name="AValue">The value to append.</param>
    procedure Enqueue(const AValue: T);
    ///  <summary>Retrieves the element from the bottom of the queue.</summary>
    ///  <returns>The value at the bottom of the queue.</returns>
    ///  <remarks>This method removes the element from the bottom of the queue.</remarks>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The queue is empty.</exception>
    function Dequeue(): T;
    ///  <summary>Reads the element from the bottom of the queue.</summary>
    ///  <returns>The value at the bottom of the queue.</returns>
    ///  <remarks>This method does not remove the element from the bottom of the queue. It merely reads it's value.</remarks>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The queue is empty.</exception>
    function Peek(): T;
    ///  <summary>Checks whether the queue contains a given value.</summary>
    ///  <param name="AValue">The value to check.</param>
    ///  <returns><c>True</c> if the value was found in the queue; <c>False</c> otherwise.</returns>
    function Contains(const AValue: T): Boolean;
  end;
  ///  <summary>The Enex interface that defines the behavior of a <c>priority queue</c>.</summary>
  ///  <remarks>This interface is implemented by all collections that provide the functionality of a <c>priority queue</c>.</remarks>
  IPriorityQueue<TPriority, TValue> = interface(IEnexAssociativeCollection<TPriority, TValue>)
    ///  <summary>Clears the contents of the priority queue.</summary>
    procedure Clear();
    ///  <summary>Adds an element to the priority queue.</summary>
    ///  <param name="AValue">The value to append.</param>
    ///  <remarks>The lowest possible priority of the element is assumed. This means that the element is appended to the top of the queue.</remarks>
    procedure Enqueue(const AValue: TValue); overload;
    ///  <summary>Adds an element to the priority queue.</summary>
    ///  <param name="AValue">The value to add.</param>
    ///  <param name="APriority">The priority of the value.</param>
    ///  <remarks>The given priority is used to calculate the position of the value in the queue. Based on the priority the element might occupy any
    ///  given position (for example it might even end up at the bottom position).</remarks>
    procedure Enqueue(const AValue: TValue; const APriority: TPriority); overload;
    ///  <summary>Retrieves the element from the bottom of the priority queue.</summary>
    ///  <returns>The value at the bottom of the priority queue.</returns>
    ///  <remarks>This method removes the element from the bottom of the priority queue.</remarks>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The queue is empty.</exception>
    function Dequeue(): TValue;
    ///  <summary>Reads the element from the bottom of the priority queue.</summary>
    ///  <returns>The value at the bottom of the priority queue.</returns>
    ///  <remarks>This method does not remove the element from the bottom of the priority queue. It merely reads it's value.</remarks>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The queue is empty.</exception>
    function Peek(): TValue;
    ///  <summary>Checks whether the priority queue contains a given value.</summary>
    ///  <param name="AValue">The value to check.</param>
    ///  <returns><c>True</c> if the value was found in the queue; <c>False</c> otherwise.</returns>
    function Contains(const AValue: TValue): Boolean;
  end;
  ///  <summary>The Enex interface that defines the behavior of a <c>set</c>.</summary>
  ///  <remarks>This interface is implemented by all collections that provide the functionality of a <c>set</c>.</remarks>
  ISet<T> = interface(IEnexCollection<T>)
    ///  <summary>Clears the contents of the set.</summary>
    procedure Clear();
    ///  <summary>Adds an element to the set.</summary>
    ///  <param name="AValue">The value to add.</param>
    ///  <remarks>If the set already contains the given value, nothing happens.</remarks>
    procedure Add(const AValue: T);
    ///  <summary>Removes a given value from the set.</summary>
    ///  <param name="AValue">The value to remove.</param>
    ///  <remarks>If the set does not contain the given value, nothing happens.</remarks>
    procedure Remove(const AValue: T);
    ///  <summary>Checks whether the set contains a given value.</summary>
    ///  <param name="AValue">The value to check.</param>
    ///  <returns><c>True</c> if the value was found in the set; <c>False</c> otherwise.</returns>
    function Contains(const AValue: T): Boolean;
  end;
  ///  <summary>The Enex interface that defines the behavior of a <c>sorted set</c>.</summary>
  ///  <remarks>This interface is implemented by all collections that provide the functionality of a <c>sorted set</c>.</remarks>
  ISortedSet<T> = interface(ISet<T>)
    ///  <summary>Returns the biggest set element.</summary>
    ///  <returns>An element from the set considered to have the biggest value.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The set is empty.</exception>
    function Max(): T;
    ///  <summary>Returns the smallest set element.</summary>
    ///  <returns>An element from the set considered to have the smallest value.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The set is empty.</exception>
    function Min(): T;
  end;
  ///  <summary>The Enex interface that defines the behavior of a <c>bag</c>.</summary>
  ///  <remarks>This interface is implemented by all collections that provide the functionality of a <c>bag</c>.</remarks>
  IBag<T> = interface(IEnexCollection<T>)
    ///  <summary>Clears the contents of the bag.</summary>
    procedure Clear();
    ///  <summary>Adds an element to the bag.</summary>
    ///  <param name="AValue">The element to add.</param>
    ///  <param name="AWeight">The weight of the element.</param>
    ///  <remarks>If the bag already contains the given value, it's stored weight is incremented to by <paramref name="AWeight"/>.
    ///  If the value of <paramref name="AWeight"/> is zero, nothing happens.</remarks>
    procedure Add(const AValue: T; const AWeight: NativeUInt = 1);
    ///  <summary>Removes an element from the bag.</summary>
    ///  <param name="AValue">The value to remove.</param>
    ///  <param name="AWeight">The weight to remove.</param>
    ///  <remarks>This method decreses the weight of the stored item by <paramref name="AWeight"/>. If the resulting weight is less
    ///  than zero or zero, the element is removed for the bag. If <paramref name="AWeight"/> is zero, nothing happens.</remarks>
    procedure Remove(const AValue: T; const AWeight: NativeUInt = 1);
    ///  <summary>Removes an element from the bag.</summary>
    ///  <param name="AValue">The value to remove.</param>
    ///  <remarks>This method completely removes an item from the bag ignoring it's stored weight. Nothing happens if the given value
    ///  is not in the bag to begin with.</remarks>
    procedure RemoveAll(const AValue: T);
    ///  <summary>Checks whether the bag contains an element with at least the required weight.</summary>
    ///  <param name="AValue">The value to check.</param>
    ///  <param name="AWeight">The smallest allowed weight.</param>
    ///  <returns><c>True</c> if the condition is met; <c>False</c> otherwise.</returns>
    ///  <remarks>This method checks whether the bag contains the given value and that the contained value has at least the
    ///  given weight.</remarks>
    function Contains(const AValue: T; const AWeight: NativeUInt = 1): Boolean;
    ///  <summary>Returns the weight of an element.</param>
    ///  <param name="AValue">The value to check.</param>
    ///  <returns>The weight of the value.</returns>
    ///  <remarks>If the value is not found in the bag, zero is returned.</remarks>
    function GetWeight(const AValue: T): NativeUInt;
    ///  <summary>Sets the weight of an element.</param>
    ///  <param name="AValue">The value to set the weight for.</param>
    ///  <param name="AWeight">The new weight.</param>
    ///  <remarks>If the value is not found in the bag, this method acts like an <c>Add</c> operation; otherwise
    ///  the weight of the stored item is adjusted.</remarks>
    procedure SetWeight(const AValue: T; const AWeight: NativeUInt);
    ///  <summary>Sets or gets the weight of an item in the bag.</summary>
    ///  <param name="AValue">The value.</param>
    ///  <remarks>If the value is not found in the bag, this method acts like an <c>Add</c> operation; otherwise
    ///  the weight of the stored item is adjusted.</remarks>
    property Weights[const AValue: T]: NativeUInt read GetWeight write SetWeight; default;
  end;
  ///  <summary>The Enex interface that defines the basic behavior of all <c>map</c>-like collections.</summary>
  ///  <remarks>This interface is inherited by all interfaces that provide <c>map</c>-like functionality.</remarks>
  IMap<TKey, TValue> = interface(IEnexAssociativeCollection<TKey, TValue>)
    ///  <summary>Clears the contents of the map.</summary>
    procedure Clear();
{$IF CompilerVersion < 22}
    ///  <summary>Adds a key-value pair to the map.</summary>
    ///  <param name="APair">The key-value pair to add.</param>
    ///  <exception cref="Collections.Base|EDuplicateKeyException">The map already contains a pair with the given key.</exception>
    procedure Add(const APair: TPair<TKey, TValue>); overload;
{$IFEND}
    ///  <summary>Adds a key-value pair to the map.</summary>
    ///  <param name="AKey">The key of pair.</param>
    ///  <param name="AValue">The value associated with the key.</param>
    ///  <exception cref="Collections.Base|EDuplicateKeyException">The map already contains a pair with the given key.</exception>
    procedure Add(const AKey: TKey; const AValue: TValue); overload;
    ///  <summary>Removes a key-value pair using a given key.</summary>
    ///  <param name="AKey">The key of pair.</param>
    ///  <remarks>If the specified key was not found in the map, nothing happens.</remarks>
    procedure Remove(const AKey: TKey);
    ///  <summary>Checks whether the map contains a key-value pair identified by the given key.</summary>
    ///  <param name="AKey">The key to check for.</param>
    ///  <returns><c>True</c> if the map contains a pair identified by the given key; <c>False</c> otherwise.</returns>
    function ContainsKey(const AKey: TKey): Boolean;
    ///  <summary>Checks whether the map contains a key-value pair that contains a given value.</summary>
    ///  <param name="AValue">The value to check for.</param>
    ///  <returns><c>True</c> if the map contains a pair containing the given value; <c>False</c> otherwise.</returns>
    ///  <remarks>This operation should be avoided. Its perfomance is poor is most map implementations.</remarks>
    function ContainsValue(const AValue: TValue): Boolean;
  end;
  ///  <summary>The Enex interface that defines the behavior of a <c>dictionary</c>.</summary>
  ///  <remarks>This interface is implemented by all collections that provide the functionality of a <c>dictionary</c>.</remarks>
  IDictionary<TKey, TValue> = interface(IMap<TKey, TValue>)
    ///  <summary>Tries to obtain the value associated with a given key.</summary>
    ///  <param name="AKey">The key for which to try to retreive the value.</param>
    ///  <param name="AFoundValue">The found value (if the result is <c>True</c>).</param>
    ///  <returns><c>True</c> if the dictionary contains a value for the given key; <c>False</c> otherwise.</returns>
    function TryGetValue(const AKey: TKey; out AFoundValue: TValue): Boolean;
    ///  <summary>Returns the value associated with the given key.</summary>
    ///  <param name="AKey">The key for which to try to retreive the value.</param>
    ///  <returns>The value associated with the key.</returns>
    ///  <exception cref="Collections.Base|EKeyNotFoundException">The key is not found in the dictionary.</exception>
    function GetItem(const AKey: TKey): TValue;
    ///  <summary>Sets the value for a given key.</summary>
    ///  <param name="AKey">The key for which to set the value.</param>
    ///  <param name="AValue">The value to set.</param>
    ///  <remarks>If the dictionary does not contain the key, this method acts like <c>Add</c>; otherwise the
    ///  value of the specified key is modified.</remarks>
    procedure SetItem(const AKey: TKey; const AValue: TValue);
    ///  <summary>Gets or sets the value for a given key.</summary>
    ///  <param name="AKey">The key for to operate on.</param>
    ///  <returns>The value associated with the key.</returns>
    ///  <remarks>If the dictionary does not contain the key, this method acts like <c>Add</c> if assignment is done to this property;
    ///  otherwise the value of the specified key is modified.</remarks>
    ///  <exception cref="Collections.Base|EKeyNotFoundException">The trying to read the value of a key that is
    ///  not found in the dictionary.</exception>
    property Items[const AKey: TKey]: TValue read GetItem write SetItem; default;
  end;
  ///  <summary>The Enex interface that defines the behavior of a <c>bidirectional dictionary</c>.</summary>
  ///  <remarks>This interface is implemented by all collections that provide the functionality of a <c>bidirectional dictionary</c>. In a
  ///  <c>bidirectional dictionary</c>, both the key and the value are treated as "keys".</remarks>
  IBidiDictionary<TKey, TValue> = interface(IMap<TKey, TValue>)
    ///  <summary>Removes a key-value pair using a given key.</summary>
    ///  <param name="AKey">The key (and its associated value) to remove.</param>
    procedure RemoveKey(const AKey: TKey);
    ///  <summary>Removes a key-value pair using a given value.</summary>
    ///  <param name="AValue">The value (and its associated key) to remove.</param>
    procedure RemoveValue(const AValue: TValue);
    ///  <summary>Removes a specific key-value combination.</summary>
    ///  <param name="AKey">The key to remove.</param>
    ///  <param name="AValue">The value to remove.</param>
    ///  <remarks>This method only remove a key-value combination if that combination actually exists in the dictionary.
    ///  If the key is associated with another value, nothing happens.</remarks>
    procedure Remove(const AKey: TKey; const AValue: TValue); overload;
{$IF CompilerVersion < 22}
    ///  <summary>Removes a key-value combination.</summary>
    ///  <param name="APair">The pair to remove.</param>
    ///  <remarks>This method only remove a key-value combination if that combination actually exists in the dictionary.
    ///  If the key is associated with another value, nothing happens.</remarks>
    procedure Remove(const APair: TPair<TKey, TValue>); overload;
{$IFEND}
    ///  <summary>Checks whether the map contains the given key-value combination.</summary>
    ///  <param name="AKey">The key associated with the value.</param>
    ///  <param name="AValue">The value associated with the key.</param>
    ///  <returns><c>True</c> if the dictionary contains the given association; <c>False</c> otherwise.</returns>
    function ContainsPair(const AKey: TKey; const AValue: TValue): Boolean; overload;
{$IF CompilerVersion < 22}
    ///  <summary>Checks whether the map contains a given key-value combination.</summary>
    ///  <param name="APair">The key-value pair combination.</param>
    ///  <returns><c>True</c> if the dictionary contains the given association; <c>False</c> otherwise.</returns>
    function ContainsPair(const APair: TPair<TKey, TValue>): Boolean; overload;
{$IFEND}
    ///  <summary>Tries to obtain the value associated with a given key.</summary>
    ///  <param name="AKey">The key for which to try to retreive the value.</param>
    ///  <param name="AFoundValue">The found value (if the result is <c>True</c>).</param>
    ///  <returns><c>True</c> if the dictionary contains a value for the given key; <c>False</c> otherwise.</returns>
    function TryGetValue(const AKey: TKey; out AFoundValue: TValue): Boolean;
    ///  <summary>Returns the value associated with a key.</summary>
    ///  <param name="AKey">The key for which to obtain the associated value.</param>
    ///  <returns>The associated value.</returns>
    ///  <exception cref="Collections.Base|EKeyNotFoundException">The key is not found in the collection.</exception>
    function GetValue(const AKey: TKey): TValue;
    ///  <summary>Sets the value for a given key.</summary>
    ///  <param name="AKey">The key for which to set the value.</param>
    ///  <param name="AValue">The value to set.</param>
    ///  <remarks>If the dictionary does not contain the key, this method acts like <c>Add</c>; otherwise the
    ///  value of the specified key is modified.</remarks>
    ///  <exception cref="Collections.Base|EDuplicateKeyException">The new value is already used by another key.</exception>
    procedure SetValue(const AKey: TKey; const AValue: TValue);
    ///  <summary>Returns the value associated with a key.</summary>
    ///  <param name="AKey">The key for which to obtain the associated value.</param>
    ///  <returns>The associated value.</returns>
    ///  <exception cref="Collections.Base|EKeyNotFoundException">The key is not found in the collection.</exception>
    property ByKey[const AKey: TKey]: TValue read GetValue write SetValue;
    ///  <summary>Tries to obtain the key associated with a given value.</summary>
    ///  <param name="AValue">The value for which to try to retreive the key.</param>
    ///  <param name="AFoundKey">The found key (if the result is <c>True</c>).</param>
    ///  <returns><c>True</c> if the dictionary contains a key for the given value; <c>False</c> otherwise.</returns>
    function TryGetKey(const AValue: TValue; out AFoundKey: TKey): Boolean;
    ///  <summary>Returns the key associated with a value.</summary>
    ///  <param name="AValue">The value for which to obtain the associated key.</param>
    ///  <returns>The associated key.</returns>
    ///  <exception cref="Collections.Base|EKeyNotFoundException">The value is not found in the collection.</exception>
    function GetKey(const AValue: TValue): TKey;
    ///  <summary>Sets the key for a given value.</summary>
    ///  <param name="AValue">The value for which to set the key.</param>
    ///  <param name="AKey">The key to set.</param>
    ///  <remarks>If the dictionary does not contain the value, this method acts like <c>Add</c>; otherwise the
    ///  key of the specified value is modified.</remarks>
    ///  <exception cref="Collections.Base|EDuplicateKeyException">The new key is already used by another value.</exception>
    procedure SetKey(const AValue: TValue; const AKey: TKey);
    ///  <summary>Returns the key associated with a value.</summary>
    ///  <param name="AValue">The value for which to obtain the associated key.</param>
    ///  <returns>The associated key.</returns>
    ///  <exception cref="Collections.Base|EKeyNotFoundException">The value is not found in the collection.</exception>
    property ByValue[const AValue: TValue]: TKey read GetKey write SetKey;
  end;
  ///  <summary>The Enex interface that defines the basic behavior of all <c>map</c>-like collections that associate a
  ///  key with multiple values.</summary>
  ///  <remarks>This interface is inherited by all interfaces that provide <c>multi-map</c>-like functionality.</remarks>
  ICollectionMap<TKey, TValue> = interface(IMap<TKey, TValue>)
    ///  <summary>Removes a key-value pair using a given key and value.</summary>
    ///  <param name="AKey">The key associated with the value.</param>
    ///  <param name="AValue">The value to remove.</param>
    ///  <remarks>A multi-map allows storing multiple values for a given key. This method allows removing only the
    ///  specified value from the collection of values associated with the given key.</remarks>
    procedure Remove(const AKey: TKey; const AValue: TValue); overload;
{$IF CompilerVersion < 22}
    ///  <summary>Removes a key-value pair using a given key and value.</summary>
    ///  <param name="APair">The key and its associated value to remove.</param>
    ///  <remarks>A multi-map allows storing multiple values for a given key. This method allows removing only the
    ///  specified value from the collection of values associated with the given key.</remarks>
    procedure Remove(const APair: TPair<TKey, TValue>); overload;
{$IFEND}
    ///  <summary>Checks whether the multi-map contains a given key-value combination.</summary>
    ///  <param name="AKey">The key associated with the value.</param>
    ///  <param name="AValue">The value associated with the key.</param>
    ///  <returns><c>True</c> if the map contains the given association; <c>False</c> otherwise.</returns>
    function ContainsValue(const AKey: TKey; const AValue: TValue): Boolean; overload;
{$IF CompilerVersion < 22}
    ///  <summary>Checks whether the multi-map contains a given key-value combination.</summary>
    ///  <param name="APair">The key-value pair to check for.</param>
    ///  <returns><c>True</c> if the map contains the given association; <c>False</c> otherwise.</returns>
    function ContainsValue(const APair: TPair<TKey, TValue>): Boolean; overload;
{$IFEND}
  end;
  ///  <summary>The Enex interface that defines the behavior of a <c>bidirectional multi-map</c>.</summary>
  ///  <remarks>This interface is implemented by all collections that provide the functionality of a <c>bidirectional multi-map</c>. In a
  ///  <c>bidirectional multi-map</c>, both the key and the value are treated as "keys".</remarks>
  IBidiMap<TKey, TValue> = interface(IMap<TKey, TValue>)
    ///  <summary>Removes a key-value pair using a given key.</summary>
    ///  <param name="AKey">The key (and its associated values) to remove.</param>
    ///  <remarks>This method removes all the values that are associated with the given key. The rule set's cleanup
    ///  routines are used to cleanup the values that are dropped from the map.</remarks>
    procedure RemoveKey(const AKey: TKey);
    ///  <summary>Removes a key-value pair using a given value.</summary>
    ///  <param name="AValue">The value (and its associated keys) to remove.</param>
    ///  <remarks>This method removes all the keys that are associated with the given value. The rule set's cleanup
    ///  routines are used to cleanup the keys that are dropped from the map.</remarks>
    procedure RemoveValue(const AValue: TValue);
    ///  <summary>Removes a specific key-value combination.</summary>
    ///  <param name="AKey">The key to remove.</param>
    ///  <param name="AValue">The value to remove.</param>
    ///  <remarks>This method only remove a key-value combination if that combination actually exists in the dictionary.
    ///  If the key is associated with another value, nothing happens.</remarks>
    procedure Remove(const AKey: TKey; const AValue: TValue); overload;
{$IF CompilerVersion < 22}
    ///  <summary>Removes a key-value combination.</summary>
    ///  <param name="APair">The pair to remove.</param>
    ///  <remarks>This method only remove a key-value combination if that combination actually exists in the dictionary.
    ///  If the key is associated with another value, nothing happens.</remarks>
    procedure Remove(const APair: TPair<TKey, TValue>); overload;
{$IFEND}
    ///  <summary>Checks whether the map contains the given key-value combination.</summary>
    ///  <param name="AKey">The key associated with the value.</param>
    ///  <param name="AValue">The value associated with the key.</param>
    ///  <returns><c>True</c> if the map contains the given association; <c>False</c> otherwise.</returns>
    function ContainsPair(const AKey: TKey; const AValue: TValue): Boolean; overload;
{$IF CompilerVersion < 22}
    ///  <summary>Checks whether the map contains a given key-value combination.</summary>
    ///  <param name="APair">The key-value pair combination.</param>
    ///  <returns><c>True</c> if the map contains the given association; <c>False</c> otherwise.</returns>
    function ContainsPair(const APair: TPair<TKey, TValue>): Boolean; overload;
{$IFEND}
    ///  <summary>Returns the collection of values associated with a key.</summary>
    ///  <param name="AKey">The key for which to obtain the associated values.</param>
    ///  <returns>An Enex collection that contains the values associated with this key.</returns>
    ///  <exception cref="Collections.Base|EKeyNotFoundException">The key is not found in the collection.</exception>
    function GetValueList(const AKey: TKey): IEnexCollection<TValue>;
    ///  <summary>Returns the collection of values associated with a key.</summary>
    ///  <param name="AKey">The key for which to obtain the associated values.</param>
    ///  <returns>An Enex collection that contains the values associated with this key.</returns>
    ///  <exception cref="Collections.Base|EKeyNotFoundException">The key is not found in the collection.</exception>
    property ByKey[const AKey: TKey]: IEnexCollection<TValue> read GetValueList;
    ///  <summary>Returns the collection of keys associated with a value.</summary>
    ///  <param name="AValue">The value for which to obtain the associated keys.</param>
    ///  <returns>An Enex collection that contains the values associated with this key.</returns>
    ///  <exception cref="Collections.Base|EKeyNotFoundException">The value is not found in the collection.</exception>
    function GetKeyList(const AValue: TValue): IEnexCollection<TKey>;
    ///  <summary>Returns the collection of keys associated with a value.</summary>
    ///  <param name="AValue">The value for which to obtain the associated keys.</param>
    ///  <returns>An Enex collection that contains the values associated with this key.</returns>
    ///  <exception cref="Collections.Base|EKeyNotFoundException">The value is not found in the collection.</exception>
    property ByValue[const AValue: TValue]: IEnexCollection<TKey> read GetKeyList;
  end;
  ///  <summary>The Enex interface that defines the behavior of a <c>multi-map</c>.</summary>
  ///  <remarks>This interface is implemented by all collections that provide the functionality of a <c>multi-map</c>. In a
  ///  <c>multi-map</c>, a key is associated with multiple values, not just one.</remarks>
  IMultiMap<TKey, TValue> = interface(ICollectionMap<TKey, TValue>)
    ///  <summary>Returns the collection of values associated with a key.</summary>
    ///  <param name="AKey">The key for which to obtain the associated values.</param>
    ///  <returns>An Enex collection that contains the values associated with this key.</returns>
    ///  <exception cref="Collections.Base|EKeyNotFoundException">The key is not found in the collection.</exception>
    function GetItemList(const AKey: TKey): IEnexIndexedCollection<TValue>;
    ///  <summary>Returns the collection of values associated with a key.</summary>
    ///  <param name="AKey">The key for which to obtain the associated values.</param>
    ///  <returns>An Enex collection that contains the values associated with this key.</returns>
    ///  <exception cref="Collections.Base|EKeyNotFoundException">The key is not found in the collection.</exception>
    property Items[const AKey: TKey]: IEnexIndexedCollection<TValue> read GetItemList; default;
    ///  <summary>Tries to extract the collection of values associated with a key.</summary>
    ///  <param name="AKey">The key for which to obtain the associated values.</param>
    ///  <param name="AValues">The Enex collection that stores the associated values.</param>
    ///  <returns><c>True</c> if the key exists in the collection; <c>False</c> otherwise;</returns>
    function TryGetValues(const AKey: TKey; out AValues: IEnexIndexedCollection<TValue>): Boolean; overload;
    ///  <summary>Tries to extract the collection of values associated with a key.</summary>
    ///  <param name="AKey">The key for which to obtain the associated values.</param>
    ///  <returns>The associated collection if the key if valid; an empty collection otherwise.</returns>
    function TryGetValues(const AKey: TKey): IEnexIndexedCollection<TValue>; overload;
  end;
  ///  <summary>The Enex interface that defines the behavior of a <c>distinct multi-map</c>.</summary>
  ///  <remarks>This interface is implemented by all collections that provide the functionality of a <c>distinct multi-map</c>. In a
  ///  <c>dictinct multi-map</c>, a key is associated with multiple distinct values.</remarks>
  IDistinctMultiMap<TKey, TValue> = interface(ICollectionMap<TKey, TValue>)
    ///  <summary>Returns the collection of values associated with a key.</summary>
    ///  <param name="AKey">The key for which to obtain the associated values.</param>
    ///  <returns>An Enex collection that contains the values associated with this key.</returns>
    ///  <exception cref="Collections.Base|EKeyNotFoundException">The key is not found in the collection.</exception>
    function GetItemList(const Key: TKey): IEnexCollection<TValue>;
    ///  <summary>Returns the collection of values associated with a key.</summary>
    ///  <param name="AKey">The key for which to obtain the associated values.</param>
    ///  <returns>An Enex collection that contains the values associated with this key.</returns>
    ///  <exception cref="Collections.Base|EKeyNotFoundException">The key is not found in the collection.</exception>
    property Items[const Key: TKey]: IEnexCollection<TValue> read GetItemList; default;
    ///  <summary>Tries to extract the collection of values associated with a key.</summary>
    ///  <param name="AKey">The key for which to obtain the associated values.</param>
    ///  <param name="AValues">The Enex collection that stores the associated values.</param>
    ///  <returns><c>True</c> if the key exists in the collection; <c>False</c> otherwise;</returns>
    function TryGetValues(const AKey: TKey; out AValues: IEnexCollection<TValue>): Boolean; overload;
    ///  <summary>Tries to extract the collection of values associated with a key.</summary>
    ///  <param name="AKey">The key for which to obtain the associated values.</param>
    ///  <returns>The associated collection if the key if valid; an empty collection otherwise.</returns>
    function TryGetValues(const AKey: TKey): IEnexCollection<TValue>; overload;
  end;
  ///  <summary>The Enex interface that defines the behavior of a <c>list</c>.</summary>
  ///  <remarks>This interface is implemented by all collections that provide the functionality of a <c>list</c>.</remarks>
  IList<T> = interface(IEnexIndexedCollection<T>)
    ///  <summary>Clears the contents of the list.</summary>
    procedure Clear();
    ///  <summary>Appends an element to the list.</summary>
    ///  <param name="AValue">The value to append.</param>
    procedure Add(const AValue: T); overload;
    ///  <summary>Appends the elements from a collection to the list.</summary>
    ///  <param name="ACollection">The values to append.</param>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="ACollection"/> is <c>nil</c>.</exception>
    procedure Add(const ACollection: IEnumerable<T>); overload;
    ///  <summary>Inserts an element into the list.</summary>
    ///  <param name="AIndex">The index to insert to.</param>
    ///  <param name="AValue">The value to insert.</param>
    ///  <remarks>All elements starting with <paramref name="AIndex"/> are moved to the right by one and then
    ///  <paramref name="AValue"/> is placed at position <paramref name="AIndex"/>.</remarks>
    ///  <exception cref="SysUtils|EArgumentOutOfRangeException"><paramref name="AIndex"/> is out of bounds.</exception>
    procedure Insert(const AIndex: NativeInt; const AValue: T); overload;
    ///  <summary>Inserts the elements of a collection into the list.</summary>
    ///  <param name="AIndex">The index to insert to.</param>
    ///  <param name="ACollection">The values to insert.</param>
    ///  <remarks>All elements starting with <paramref name="AIndex"/> are moved to the right by the length of
    ///  <paramref name="ACollection"/> and then <paramref name="AValue"/> is placed at position <paramref name="AIndex"/>.</remarks>
    ///  <exception cref="SysUtils|EArgumentOutOfRangeException"><paramref name="AIndex"/> is out of bounds.</exception>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="ACollection"/> is <c>nil</c>.</exception>
    procedure Insert(const AIndex: NativeInt; const ACollection: IEnumerable<T>); overload;
    ///  <summary>Checks whether the list contains a given value.</summary>
    ///  <param name="AValue">The value to check.</param>
    ///  <returns><c>True</c> if the value was found in the list; <c>False</c> otherwise.</returns>
    function Contains(const AValue: T): Boolean;
    ///  <summary>Removes an element from the list at a given index.</summary>
    ///  <param name="AIndex">The index from which to remove the element.</param>
    ///  <remarks>This method removes the specified element and moves all following elements to the left by one.</remarks>
    ///  <exception cref="SysUtils|EArgumentOutOfRangeException"><paramref name="AIndex"/> is out of bounds.</exception>
    procedure RemoveAt(const AIndex: NativeInt);
    ///  <summary>Removes a given value from the list.</summary>
    ///  <param name="AValue">The value to remove.</param>
    ///  <remarks>If the list does not contain the given value, nothing happens.</remarks>
    procedure Remove(const AValue: T);
    ///  <summary>Searches for the first appearance of a given element in this list.</summary>
    ///  <param name="AValue">The value to search for.</param>
    ///  <param name="AStartIndex">The index to from which the search starts.</param>
    ///  <param name="ACount">The number of elements after the starting one to check against.</param>
    ///  <returns><c>-1</c> if the value was not found; otherwise a positive value indicating the index of the value.</returns>
    ///  <exception cref="SysUtils|EArgumentOutOfRangeException">Parameter combination is incorrect.</exception>
    function IndexOf(const AValue: T; const AStartIndex, ACount: NativeInt): NativeInt; overload;
    ///  <summary>Searches for the first appearance of a given element in this list.</summary>
    ///  <param name="AValue">The value to search for.</param>
    ///  <param name="AStartIndex">The index to from which the search starts.</param>
    ///  <returns><c>-1</c> if the value was not found; otherwise a positive value indicating the index of the value.</returns>
    ///  <exception cref="SysUtils|EArgumentOutOfRangeException"><paramref name="AStartIndex"/> is out of bounds.</exception>
    function IndexOf(const AValue: T; const AStartIndex: NativeInt): NativeInt; overload;
    ///  <summary>Searches for the first appearance of a given element in this list.</summary>
    ///  <param name="AValue">The value to search for.</param>
    ///  <returns><c>-1</c> if the value was not found; otherwise a positive value indicating the index of the value.</returns>
    function IndexOf(const AValue: T): NativeInt; overload;
    ///  <summary>Searches for the last appearance of a given element in this list.</summary>
    ///  <param name="AValue">The value to search for.</param>
    ///  <param name="AStartIndex">The index to from which the search starts.</param>
    ///  <param name="ACount">The number of elements after the starting one to check against.</param>
    ///  <returns><c>-1</c> if the value was not found; otherwise a positive value indicating the index of the value.</returns>
    ///  <exception cref="SysUtils|EArgumentOutOfRangeException">Parameter combination is incorrect.</exception>
    function LastIndexOf(const AValue: T; const AStartIndex, ACount: NativeInt): NativeInt; overload;
    ///  <summary>Searches for the last appearance of a given element in this list.</summary>
    ///  <param name="AValue">The value to search for.</param>
    ///  <param name="AStartIndex">The index to from which the search starts.</param>
    ///  <returns><c>-1</c> if the value was not found; otherwise a positive value indicating the index of the value.</returns>
    ///  <exception cref="SysUtils|EArgumentOutOfRangeException"><paramref name="AStartIndex"/> is out of bounds.</exception>
    function LastIndexOf(const AValue: T; const AStartIndex: NativeInt): NativeInt; overload;
    ///  <summary>Searches for the last appearance of a given element in this list.</summary>
    ///  <param name="AValue">The value to search for.</param>
    ///  <returns><c>-1</c> if the value was not found; otherwise a positive value indicating the index of the value.</returns>
    function LastIndexOf(const AValue: T): NativeInt; overload;
  end;
  ///  <summary>The Enex interface that defines the behavior of a <c>sorted list</c>.</summary>
  ///  <remarks>This interface is implemented by all collections that provide the functionality of a <c>sorted list</c>.
  ///  A <c>sorted list</c> maintains its elements in an ordered fashion at all times. Whenever a new element is added, it is
  ///  automatically inserted in the right position.</remarks>
  ISortedList<T> = interface(IList<T>)
    ///  <summary>Returns the biggest element.</summary>
    ///  <returns>An element from the list considered to have the biggest value. This is either the
    ///  last or the first element (depending on the sorting order).</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    function Max(): T;
    ///  <summary>Returns the smallest element.</summary>
    ///  <returns>An element from the list considered to have the smallest value. This is either the
    ///  last or the first element (depending on the sorting order).</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    function Min(): T;
  end;
  ///  <summary>A special interface implemented by collections that support the concept of capacity.</summary>
  ///  <remarks>This interface specifies a set of method that allow controlling the capactity of a collection.</remarks>
  IDynamic = interface
    ///  <summary>Returns the current capacity.</summary>
    ///  <returns>A positive number that specifies the number of elements that the collection can hold before it
    ///  needs to grow again.</returns>
    ///  <remarks>The value of this method is greater or equal to the amount of elements in the collection. If this value
    ///  if greater then the number of elements, it means that the collection has some extra capacity to operate upon.</remarks>
    function GetCapacity(): NativeInt;
    ///  <summary>Removes the excess capacity from the collection.</summary>
    ///  <remarks>This method can be called manually to force the collection to drop the extra capacity it might hold. For example,
    ///  after performing some massive operations on a big list, call this method to ensure that all extra memory held by the
    ///  collection is released.</remarks>
    procedure Shrink();
    ///  <summary>Forces the collection to increase its capacity.</summary>
    ///  <remarks>Call this method to force the collection to increase its capacity ahead of time. Manually adjusting the capacity
    ///  can be useful in certain situations. Each collection specifies its "growing" strategy. Most collections grow by a factor of two
    ///  <c>(New Capacity = Old Capacity * 2)</c>.</remarks>
    procedure Grow();
    ///  <summary>Specifies the current capacity.</summary>
    ///  <returns>A positive number that specifies the number of elements that the collection can hold before it
    ///  needs to grow again.</returns>
    ///  <remarks>The value of this property is greater or equal to the amount of elements in the collection. If this value
    ///  if greater then the number of elements, it means that the collection has some extra capacity to operate upon.</remarks>
    property Capacity: NativeInt read GetCapacity;
  end;
{$ENDREGION}
{$REGION 'Base Collection Classes'}
type
{$HINTS OFF}
  ///  <summary>Base for all reference counted objects in this package.</summary>
  ///  <remarks><see cref="Collections.Base|TRefCountedObject">Collections.Base.TRefCountedObject</see> is designed to be used as a base class for all
  ///  objects that implement interfaces and require reference counting.</remarks>
  TRefCountedObject = class abstract(TInterfacedObject, IInterface)
  private
    FKeepAliveList: TArray<IInterface>;
    FInConstruction: Boolean;
  protected
    ///  <summary>Registers a reference counted object as as keep-alive for this object.</summary>
    ///  <param name="AObject">The object to keep alive.</param>
    ///  <remarks>If <paramref name="AObject"/> is <c>nil</c> nothing happens. Otherwise, this object is
    ///  checked to have a positive reference count. If that is the case, a new interface reference is requested
    ///  and registered internally, preventing the object from being destroyed prematurely.</remarks>
    ///  <exception cref="Collections.Base|ECannotSelfReferenceException"> if trying to keep alive self.</exception>
    procedure KeepObjectAlive(const AObject: TRefCountedObject);
    ///  <summary>Unregisters a reference counted object from the keep-alive list.</summary>
    ///  <param name="AObject">The object to unregister.</param>
    ///  <param name="AFreeObject">Specifies whether to free the object if its reference reaches is zero.</param>
    ///  <remarks>If <paramref name="AObject"/> is <c>nil</c> nothing happens. Otherwise, this object is
    ///  checked to have a positive reference count. If that is the case, the help reference is released.</remarks>
    ///  <exception cref="Collections.Base|ECannotSelfReferenceException"> if trying to release self.</exception>
    procedure ReleaseObject(const AObject: TRefCountedObject;
      const AFreeObject: Boolean = false);
    ///  <summary>Extract an interafce reference for this object.</summary>
    ///  <remarks>If the reference count is zero, then no reference is extracted.</remarks>
    ///  <returns>An interface reference or <c>nil</c>.</returns>
    function ExtractReference(): IInterface;
    ///  <summary>Specifies whether the object is currently being constructed.</summary>
    ///  <returns><c>True</c> if the object is in construction; <c>False</c> otherwise.</returns>
    property Constructing: Boolean read FInConstruction;
  public
    ///  <summary>Initializes the internals of the <see cref="Collections.Base|TRefCountedObject">Collections.Base.TRefCountedObject</see> objects.</summary>
    ///  <remarks>Do not call this method directly. It is part of the object creation process.</remarks>
    class function NewInstance: TObject; override;
    ///  <summary>Initializes the internals of the <see cref="Collections.Base|TRefCountedObject">Collections.Base.TRefCountedObject</see> objects.</summary>
    ///  <remarks>Do not call this method directly. It is part of the object creation process.</remarks>
    procedure AfterConstruction; override;
  end;
{$HINTS ON}
  ///  <summary>Base class for all Enex enumerator objects.</summary>
  ///  <remarks>All Enex collection are expected to provide enumerators that derive from
  ///  this class.</remarks>
  TEnumerator<T> = class abstract(TRefCountedObject, IEnumerator<T>)
    ///  <summary>Returns the current element of the enumerated collection.</summary>
    ///  <remarks>This method is the getter for <c>Current</c> property. Use the property to obtain the element instead.</remarks>
    ///  <returns>The current element of the enumerated collection.</returns>
    function GetCurrent(): T; virtual; abstract;
    ///  <summary>Moves the enumerator to the next element of collection.</summary>
    ///  <remarks>This method is usually called by compiler generated code. Its purpose is to move the "pointer" to the next element in
    ///  the collection (if there are elements left). Also note that many specific enumerator implementations may throw various
    ///  exceptions if the enumerated collection was changed while enumerating.</remarks>
    ///  <returns><c>True</c> if the enumerator succesefully selected the next element; <c>False</c> is there are
    ///  no more elements to be enumerated.</returns>
    function MoveNext(): Boolean; virtual; abstract;
    ///  <summary>Returns the current element of the enumerated collection.</summary>
    ///  <remarks>This property can only return a valid element if <c>MoveNext</c> was priorly called and returned <c>True</c>;
    ///  otherwise the behavior of this property is undefined.
    ///  </remarks>
    ///  <returns>The current element of the enumerated collection.</returns>
    property Current: T read GetCurrent;
  end;
  ///  <summary>Procedural type used by collections to insert custom remove notification code
  ///  into inner collections.</summary>
  ///  <param name="AValue">The value being removed.</param>
  TRemoveNotification<T> = reference to procedure(const AValue: T);
  ///  <summary>Base class for all collections.</summary>
  ///  <remarks>All collections are derived from this base class. It implements most Enex operations based on
  ///  enumerability and introduces serialization support.</remarks>
  TCollection<T> = class abstract(TRefCountedObject, ICollection<T>, IEnumerable<T>)
  protected
    const CDefaultSize = 32;
    ///  <summary>Returns the number of elements in the collection.</summary>
    ///  <returns>A positive value specifying the number of elements in the collection.</returns>
    ///  <remarks>A call to this method can be costly because some
    ///  collections cannot detect the number of stored elements directly, resorting to enumerating themselves.</remarks>
    function GetCount(): NativeInt; virtual;
  public
    ///  <summary>Checks whether the collection is empty.</summary>
    ///  <returns><c>True</c> if the collection is empty; <c>False</c> otherwise.</returns>
    ///  <remarks>This method is the recommended way of detecting if the collection is empty. It is optimized
    ///  in most collections to offer a fast response.</remarks>
    function Empty(): Boolean; virtual;
    ///  <summary>Returns the single element stored in the collection.</summary>
    ///  <returns>The element in collection.</returns>
    ///  <remarks>This method checks if the collection contains just one element, in which case it is returned.</remarks>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    ///  <exception cref="Collections.Base|ECollectionNotOneException">There is more than one element in the collection.</exception>
    function Single(): T; virtual;
    ///  <summary>Returns the single element stored in the collection, or a default value.</summary>
    ///  <param name="ADefault">The default value returned if there is less or more elements in the collection.</param>
    ///  <returns>The element in the collection if the condition is satisfied; <paramref name="ADefault"/> is returned otherwise.</returns>
    ///  <remarks>This method checks if the collection contains just one element, in which case it is returned. Otherwise
    ///  the value in <paramref name="ADefault"/> is returned.</remarks>
    function SingleOrDefault(const ADefault: T): T; virtual;
    ///  <summary>Copies the values stored in the collection to a given array.</summary>
    ///  <param name="AArray">An array where to copy the contents of the collection.</param>
    ///  <remarks>This method assumes that <paramref name="AArray"/> has enough space to hold the contents of the collection.</remarks>
    ///  <exception cref="Collections.Base|EArgumentOutOfSpaceException">There array is not long enough.</exception>
    procedure CopyTo(var AArray: array of T); overload;
    ///  <summary>Copies the values stored in the collection to a given array.</summary>
    ///  <param name="AArray">An array where to copy the contents of the collection.</param>
    ///  <param name="AStartIndex">The index into the array at which the copying begins.</param>
    ///  <remarks>This method assumes that <paramref name="AArray"/> has enough space to hold the contents of the collection.</remarks>
    ///  <exception cref="SysUtils|EArgumentOutOfRangeException"><paramref name="AStartIndex"/> is out of bounds.</exception>
    ///  <exception cref="Collections.Base|EArgumentOutOfSpaceException">There array is not long enough.</exception>
    procedure CopyTo(var AArray: array of T; const AStartIndex: NativeInt); overload; virtual;
    ///  <summary>Creates a new Delphi array with the contents of the collection.</summary>
    ///  <remarks>The length of the new array is equal to the value of <c>Count</c> property.</remarks>
    function ToArray(): TArray<T>; virtual;
    ///  <summary>Returns a new enumerator object used to enumerate the collection.</summary>
    ///  <remarks>This method is usually called by compiler generated code. It's purpose is to create an enumerator
    ///  object that is used to actually traverse the collection.
    ///  Note that many collections generate enumerators that depend on the state of the collection. If the collection is changed
    ///  after the enumerator has been obtained, the enumerator is considered invalid. All subsequent operations on that enumerator
    ///  will throw exceptions.</remarks>
    ///  <returns>An enumerator object.</returns>
    function GetEnumerator(): IEnumerator<T>; virtual; abstract;
    ///  <summary>Specifies the number of elements in the collection.</summary>
    ///  <returns>A positive value specifying the number of elements in the collection.</returns>
    ///  <remarks>Accesing this property can be costly because some
    ///  collections cannot detect the number of stored elements directly, resorting to enumerating themselves.</remarks>
    property Count: NativeInt read GetCount;
  end;
  ///  <summary>Base class for all non-associative Enex collections.</summary>
  ///  <remarks>All normal Enex collections (ex. list or stack) are derived from this base class.
  ///  It implements the extended Enex operations based on enumerability and introduces functional
  ///  serialization support.</remarks>
  TEnexCollection<T> = class abstract(TCollection<T>, IComparable, IEnexCollection<T>)
  private
    FElementRules: TRules<T>;
    FRemoveNotification: TRemoveNotification<T>;
  protected
    ///  <summary>Specifies a custom remove notification method that will be called by this
    ///  collection when elements are removed.</summary>
    ///  <returns>The notification method.</returns>
    property RemoveNotification: TRemoveNotification<T> read FRemoveNotification write FRemoveNotification;
    ///  <summary>Compares two values for equality.</summary>
    ///  <param name="ALeft">The first value.</param>
    ///  <param name="ARight">The second value.</param>
    ///  <returns><c>True</c> if the values are equal; <c>False</c> otherwise.</returns>
    ///  <remarks>This method uses the equality comparer. If such a comparer was not provided
    ///  a default one is requested.</remarks>
    function ElementsAreEqual(const ALeft, ARight: T): Boolean;
    ///  <summary>Compares two values.</summary>
    ///  <param name="ALeft">The first value.</param>
    ///  <param name="ARight">The second value.</param>
    ///  <returns>A value less than zero if <paramref name="ALeft"/> is less than <paramref name="ARight"/>.
    ///  A value greater than zero if <paramref name="ALeft"/> is greater than <paramref name="ARight"/>. Zero if
    ///  <paramref name="ALeft"/> is equal to <paramref name="ARight"/>.</returns>
    ///  <remarks>This method uses the comparer. If such a comparer was not provided
    ///  a default one is requested.</remarks>
    function CompareElements(const ALeft, ARight: T): NativeInt;
    ///  <summary>Generates a hash code for the given value.</summary>
    ///  <param name="AValue">The value.</param>
    ///  <returns>The calculated hash code.</returns>
    ///  <remarks>This method uses the equality comparer. If such a comparer was not provided
    ///  a default one is requested.</remarks>
    function GetElementHashCode(const AValue: T): NativeInt; overload;
    ///  <summary>Specifies the rule set that describes the stored elements.</summary>
    ///  <returns>A rule set describing the stored elements.</returns>
    property ElementRules: TRules<T> read FElementRules;
    ///  <summary>Override in descendant classed to properly handle elements that are removed from
    ///  the collection.</summary>
    ///  <param name="AElement">The element being removed.</param>
    ///  <remarks>This method is called by the collection when an element is removed and the caller has
    ///  no possibility of obtaining it. For example, a call to <c>Clear</c> calls this method for each element
    ///  of the collection.</remarks>
    procedure HandleElementRemoved(const AElement: T); virtual;
    ///  <summary>Call this method in descendant collections to properly invoke the removal mechanism.</summary>
    ///  <param name="AElement">The element being removed.</param>
    ///  <remarks>This method verifies if a custom removal notification is registered and calls it. Otherwise the normal
    ///  removal mechanisms are involved.</remarks>
    procedure NotifyElementRemoved(const AElement: T);
  public
    ///  <summary>Instantiates this class.</summary>
    ///  <remarks>The default comparer and equality comparer are requested if this constructor is used. Do not call this method if
    ///  you don't know what you are doing.</remarks>
    constructor Create(); overload;
    ///  <summary>Instantiates this class.</summary>
    ///  <param name="ARules">The rules set used by the collection.</param>
    ///  <remarks>The provided rules set is used by this collection. This constructor must be called from descendent collections.</remarks>
    constructor Create(const ARules: TRules<T>); overload;
    ///  <summary>Returns the biggest element.</summary>
    ///  <returns>An element from the collection considered to have the biggest value.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    function Max(): T; virtual;
    ///  <summary>Returns the smallest element.</summary>
    ///  <returns>An element from the collection considered to have the smallest value.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    function Min(): T; virtual;
    ///  <summary>Returns the first element.</summary>
    ///  <returns>The first element in collection.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    function First(): T; virtual;
    ///  <summary>Returns the first element or a default if the collection is empty.</summary>
    ///  <param name="ADefault">The default value returned if the collection is empty.</param>
    ///  <returns>The first element in collection if the collection is not empty; otherwise <paramref name="ADefault"/> is returned.</returns>
    function FirstOrDefault(const ADefault: T): T; virtual;
    ///  <summary>Returns the first element that satisfies the given predicate.</summary>
    ///  <param name="APredicate">The predicate to use.</param>
    ///  <returns>The first element that satisfies the given predicate.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    ///  <exception cref="Collections.Base|ECollectionFilteredEmptyException">No elements satisfy the predicate.</exception>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="APredicate"/> is <c>nil</c>.</exception>
    function FirstWhere(const APredicate: TFunc<T, Boolean>): T; virtual;
    ///  <summary>Returns the first element that satisfies the given predicate or a default value.</summary>
    ///  <param name="APredicate">The predicate to use.</param>
    ///  <param name="ADefault">The default value.</param>
    ///  <returns>The first element that satisfies the given predicate; or <paramref name="ADefault"/> otherwise.</returns>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="APredicate"/> is <c>nil</c>.</exception>
    function FirstWhereOrDefault(const APredicate: TFunc<T, Boolean>; const ADefault: T): T; virtual;
    ///  <summary>Returns the first element that does not satisfy the given predicate.</summary>
    ///  <param name="APredicate">The predicate to use.</param>
    ///  <returns>The first element that does not satisfy the given predicate.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    ///  <exception cref="Collections.Base|ECollectionFilteredEmptyException">No elements that do not satisfy the predicate.</exception>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="APredicate"/> is <c>nil</c>.</exception>
    function FirstWhereNot(const APredicate: TFunc<T, Boolean>): T;
    ///  <summary>Returns the first element that does not satisfy the given predicate or a default value.</summary>
    ///  <param name="APredicate">The predicate to use.</param>
    ///  <param name="ADefault">The default value.</param>
    ///  <returns>The first element that does not satisfy the given predicate; or <paramref name="ADefault"/> otherwise.</returns>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="APredicate"/> is <c>nil</c>.</exception>
    function FirstWhereNotOrDefault(const APredicate: TFunc<T, Boolean>; const ADefault: T): T;
    ///  <summary>Returns the first element lower than a given value.</summary>
    ///  <param name="ABound">The value to compare against.</param>
    ///  <returns>The first element that satisfies the given condition.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    ///  <exception cref="Collections.Base|ECollectionFilteredEmptyException">No elements satisfy the condition.</exception>
    function FirstWhereLower(const ABound: T): T;
    ///  <summary>Returns the first element lower than a given value or a default.</summary>
    ///  <param name="ABound">The value to compare against.</param>
    ///  <param name="ADefault">The default value.</param>
    ///  <returns>The first element that satisfies the given condition; or <paramref name="ADefault"/> otherwise.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    ///  <exception cref="Collections.Base|ECollectionFilteredEmptyException">No elements satisfy the condition.</exception>
    function FirstWhereLowerOrDefault(const ABound: T; const ADefault: T): T;
    ///  <summary>Returns the first element lower than or equal to a given value.</summary>
    ///  <param name="ABound">The value to compare against.</param>
    ///  <returns>The first element that satisfies the given condition.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    ///  <exception cref="Collections.Base|ECollectionFilteredEmptyException">No elements satisfy the condition.</exception>
    function FirstWhereLowerOrEqual(const ABound: T): T;
    ///  <summary>Returns the first element lower than or equal to a given value or a default.</summary>
    ///  <param name="ABound">The value to compare against.</param>
    ///  <param name="ADefault">The default value.</param>
    ///  <returns>The first element that satisfies the given condition; or <paramref name="ADefault"/> otherwise.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    ///  <exception cref="Collections.Base|ECollectionFilteredEmptyException">No elements satisfy the condition.</exception>
    function FirstWhereLowerOrEqualOrDefault(const ABound: T; const ADefault: T): T;
    ///  <summary>Returns the first element greater than a given value.</summary>
    ///  <param name="ABound">The value to compare against.</param>
    ///  <returns>The first element that satisfies the given condition.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    ///  <exception cref="Collections.Base|ECollectionFilteredEmptyException">No elements satisfy the condition.</exception>
    function FirstWhereGreater(const ABound: T): T;
    ///  <summary>Returns the first element greater than a given value or a default.</summary>
    ///  <param name="ABound">The value to compare against.</param>
    ///  <param name="ADefault">The default value.</param>
    ///  <returns>The first element that satisfies the given condition; or <paramref name="ADefault"/> otherwise.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    ///  <exception cref="Collections.Base|ECollectionFilteredEmptyException">No elements satisfy the condition.</exception>
    function FirstWhereGreaterOrDefault(const ABound: T; const ADefault: T): T;
    ///  <summary>Returns the first element greater than or equal to a given value.</summary>
    ///  <param name="ABound">The value to compare against.</param>
    ///  <returns>The first element that satisfies the given condition.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    ///  <exception cref="Collections.Base|ECollectionFilteredEmptyException">No elements satisfy the condition.</exception>
    function FirstWhereGreaterOrEqual(const ABound: T): T;
    ///  <summary>Returns the first element greater than or equal to a given value or a default.</summary>
    ///  <param name="ABound">The value to compare against.</param>
    ///  <param name="ADefault">The default value.</param>
    ///  <returns>The first element that satisfies the given condition; or <paramref name="ADefault"/> otherwise.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    ///  <exception cref="Collections.Base|ECollectionFilteredEmptyException">No elements satisfy the condition.</exception>
    function FirstWhereGreaterOrEqualOrDefault(const ABound: T; const ADefault: T): T;
    ///  <summary>Returns the first element situated within the given bounds.</summary>
    ///  <param name="ALower">The lower bound.</param>
    ///  <param name="AHigher">The higher bound.</param>
    ///  <returns>The first element that satisfies the given condition.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    ///  <exception cref="Collections.Base|ECollectionFilteredEmptyException">No elements satisfy the condition.</exception>
    function FirstWhereBetween(const ALower, AHigher: T): T;
    ///  <summary>Returns the first element situated within the given bounds or a default.</summary>
    ///  <param name="ALower">The lower bound.</param>
    ///  <param name="AHigher">The higher bound.</param>
    ///  <param name="ADefault">The default value.</param>
    ///  <returns>The first element that satisfies the given condition; or <paramref name="ADefault"/> otherwise.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    ///  <exception cref="Collections.Base|ECollectionFilteredEmptyException">No elements satisfy the condition.</exception>
    function FirstWhereBetweenOrDefault(const ALower, AHigher: T; const ADefault: T): T;
    ///  <summary>Returns the last element.</summary>
    ///  <returns>The last element in collection.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    function Last(): T; virtual;
    ///  <summary>Returns the last element or a default if the collection is empty.</summary>
    ///  <param name="ADefault">The default value returned if the collection is empty.</param>
    ///  <returns>The last element in collection if the collection is not empty; otherwise <paramref name="ADefault"/> is returned.</returns>
    function LastOrDefault(const ADefault: T): T; virtual;
    ///  <summary>Aggregates a value based on the collection's elements.</summary>
    ///  <param name="AAggregator">The aggregator method.</param>
    ///  <returns>A value that contains the collection's aggregated value.</returns>
    ///  <remarks>This method returns the first element if the collection only has one element. Otherwise,
    ///  <paramref name="AAggregator"/> is invoked for each two elements (first and second; then the result of the first two
    ///  and the third, and so on). The simplest example of aggregation is the "sum" operation where you can obtain the sum of all
    ///  elements in the value.</remarks>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="AAggregator"/> is <c>nil</c>.</exception>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    function Aggregate(const AAggregator: TFunc<T, T, T>): T; virtual;
    ///  <summary>Aggregates a value based on the collection's elements.</summary>
    ///  <param name="AAggregator">The aggregator method.</param>
    ///  <param name="ADefault">The default value returned if the collection is empty.</param>
    ///  <returns>A value that contains the collection's aggregated value. If the collection is empty, <paramref name="ADefault"/> is returned.</returns>
    ///  <remarks>This method returns the first element if the collection only has one element. Otherwise,
    ///  <paramref name="AAggregator"/> is invoked for each two elements (first and second; then the result of the first two
    ///  and the third, and so on). The simplest example of aggregation is the "sum" operation where you can obtain the sum of all
    ///  elements in the value.</remarks>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="AAggregator"/> is <c>nil</c>.</exception>
    function AggregateOrDefault(const AAggregator: TFunc<T, T, T>; const ADefault: T): T; virtual;
    ///  <summary>Returns the element at a given position.</summary>
    ///  <param name="AIndex">The index from which to return the element.</param>
    ///  <returns>The element at the specified position.</returns>
    ///  <remarks>This method is slow for collections that cannot reference their elements by indexes, for example linked lists.</remarks>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    ///  <exception cref="SysUtils|EArgumentOutOfRangeException"><paramref name="AIndex"/> is out of bounds.</exception>
    function ElementAt(const AIndex: NativeInt): T; virtual;
    ///  <summary>Returns the element at a given position.</summary>
    ///  <param name="AIndex">The index from which to return the element.</param>
    ///  <param name="ADefault">The default value returned if the collection is empty.</param>
    ///  <returns>The element at the specified position if the collection is not empty and the position is not out of bounds; otherwise
    ///  the value of <paramref name="ADefault"/> is returned.</returns>
    ///  <remarks>This method is slow for collections that cannot reference their elements by indexes, for example linked lists.</remarks>
    function ElementAtOrDefault(const AIndex: NativeInt; const ADefault: T): T; virtual;
    ///  <summary>Check whether at least one element in the collection satisfies a given predicate.</summary>
    ///  <param name="APredicate">The predicate to check for each element.</param>
    ///  <returns><c>True</c> if at least one element satisfies a given predicate; <c>False</c> otherwise.</returns>
    ///  <remarks>This method traverses the whole collection and checks the value of the predicate for each element. This method
    ///  stops on the first element for which the predicate returns <c>True</c>. The logical equivalent of this operation is "OR".</remarks>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="APredicate"/> is <c>nil</c>.</exception>
    function Any(const APredicate: TFunc<T, Boolean>): Boolean; virtual;
    ///  <summary>Checks that all elements in the collection satisfies a given predicate.</summary>
    ///  <param name="APredicate">The predicate to check for each element.</param>
    ///  <returns><c>True</c> if all elements satisfy a given predicate; <c>False</c> otherwise.</returns>
    ///  <remarks>This method traverses the whole collection and checks the value of the predicate for each element. This method
    ///  stops on the first element for which the predicate returns <c>False</c>. The logical equivalent of this operation is "AND".</remarks>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="APredicate"/> is <c>nil</c>.</exception>
    function All(const APredicate: TFunc<T, Boolean>): Boolean; virtual;
    ///  <summary>Checks whether the elements in this collection are equal to the elements in another collection.</summary>
    ///  <param name="ACollection">The collection to compare to.</param>
    ///  <returns><c>True</c> if the collections are equal; <c>False</c> if the collections are different.</returns>
    ///  <remarks>This method checks that each element at position X in this collection is equal to an element at position X in
    ///  the provided collection. If the number of elements in both collections is different, then the collections are considered different.
    ///  Note that comparison of element is done using the rule set used by this collection. This means that comparing this collection
    ///  to another one might yield a different result than comparing the other collection to this one.</remarks>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="ACollection"/> is <c>nil</c>.</exception>
    function EqualsTo(const ACollection: IEnumerable<T>): Boolean; virtual;
    ///  <summary>Selects only the elements that satisfy a given rule.</summary>
    ///  <param name="APredicate">The predicate that represents the rule.</param>
    ///  <returns>A new collection that contains only the elements that satisfy the given rule.</returns>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="APredicate"/> is <c>nil</c>.</exception>
    function Where(const APredicate: TFunc<T, Boolean>): IEnexCollection<T>;
    ///  <summary>Selects only the elements that do not satisfy a given rule.</summary>
    ///  <param name="APredicate">The predicate that represents the rule.</param>
    ///  <returns>A new collection that contains only the elements that do not satisfy the given rule.</returns>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="APredicate"/> is <c>nil</c>.</exception>
    function WhereNot(const APredicate: TFunc<T, Boolean>): IEnexCollection<T>;
    ///  <summary>Selects only the elements that are less than a given value.</summary>
    ///  <param name="ABound">The element to compare against.</param>
    ///  <returns>A new collection that contains only the elements that satisfy the relationship.</returns>
    function WhereLower(const ABound: T): IEnexCollection<T>;
    ///  <summary>Selects only the elements that are less than or equal to a given value.</summary>
    ///  <param name="ABound">The element to compare against.</param>
    ///  <returns>A new collection that contains only the elements that satisfy the relationship.</returns>
    function WhereLowerOrEqual(const ABound: T): IEnexCollection<T>;
    ///  <summary>Selects only the elements that are greater than a given value.</summary>
    ///  <param name="ABound">The element to compare against.</param>
    ///  <returns>A new collection that contains only the elements that satisfy the relationship.</returns>
    function WhereGreater(const ABound: T): IEnexCollection<T>;
    ///  <summary>Selects only the elements that are greater than or equal to a given value.</summary>
    ///  <param name="ABound">The element to compare against.</param>
    ///  <returns>A new collection that contains only the elements that satisfy the relationship.</returns>
    function WhereGreaterOrEqual(const ABound: T): IEnexCollection<T>;
    ///  <summary>Selects only the elements whose values are contained whithin a given interval.</summary>
    ///  <param name="ALower">The lower bound.</param>
    ///  <param name="AHigher">The upper bound.</param>
    ///  <returns>A new collection that contains only the elements that satisfy the relationship.</returns>
    ///  <remarks>The elements that are equal to the lower or upper bound are also included.</remarks>
    function WhereBetween(const ALower, AHigher: T): IEnexCollection<T>;
    ///  <summary>Selects all the elements from the collection excluding duplicates.</summary>
    ///  <returns>A new collection that contains the distinct elements.</returns>
    function Distinct(): IEnexCollection<T>; virtual;
    ///  <summary>Returns a new ordered collection that contains the elements from this collection.</summary>
    ///  <param name="AAscending">Specifies whether the elements are ordered in an ascending or descending way.</param>
    ///  <returns>A new ordered collection.</returns>
    function Ordered(const AAscending: Boolean = true): IEnexCollection<T>; overload; virtual;
    ///  <summary>Returns a new ordered collection that contains the elements from this collection.</summary>
    ///  <param name="ASortProc">The comparison method.</param>
    ///  <returns>A new ordered collection.</returns>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="ASortProc"/> is <c>nil</c>.</exception>
    function Ordered(const ASortProc: TComparison<T>): IEnexCollection<T>; overload; virtual;
    ///  <summary>Revereses the contents of the collection.</summary>
    ///  <returns>A new collection that contains the elements from this collection but in reverse order.</returns>
    function Reversed(): IEnexCollection<T>; virtual;
    ///  <summary>Concatenates this collection with another collection.</summary>
    ///  <param name="ACollection">A collection to concatenate.</param>
    ///  <returns>A new collection that contains the elements from this collection followed by elements
    ///  from the given collection.</returns>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="ACollection"/> is <c>nil</c>.</exception>
    function Concat(const ACollection: IEnexCollection<T>): IEnexCollection<T>;
    ///  <summary>Creates a new collection that contains the elements from both collections, taken a single time.</summary>
    ///  <param name="ACollection">The collection to unify with.</param>
    ///  <returns>A new collection that contains the elements from this collection followed by elements
    ///  from the given collection except the elements that already are present in this collection. This operation can be seen as
    ///  a "concat" operation followed by a "distinct" operation. </returns>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="ACollection"/> is <c>nil</c>.</exception>
    function Union(const ACollection: IEnexCollection<T>): IEnexCollection<T>;
    ///  <summary>Creates a new collection that contains the elements from this collection minus the ones in the given collection.</summary>
    ///  <param name="ACollection">The collection to exclude.</param>
    ///  <returns>A new collection that contains the elements from this collection minus those elements that are common between
    ///  this and the given collection.</returns>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="ACollection"/> is <c>nil</c>.</exception>
    function Exclude(const ACollection: IEnexCollection<T>): IEnexCollection<T>;
    ///  <summary>Creates a new collection that contains the elements that are present in both collections.</summary>
    ///  <param name="ACollection">The collection to interset with.</param>
    ///  <returns>A new collection that contains the elements that are common to both collections.</returns>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="ACollection"/> is <c>nil</c>.</exception>
    function Intersect(const ACollection: IEnexCollection<T>): IEnexCollection<T>;
    ///  <summary>Select the elements whose indexes are located in the given range.</summary>
    ///  <param name="AStart">The lower bound.</param>
    ///  <param name="AEnd">The upper bound.</param>
    ///  <returns>A new collection that contains the elements whose indexes in this collection are located between <paramref name="AStart"/>
    ///  and <paramref name="AEnd"/>. Note that this method does not check the indexes. This means that a bad combination of parameters will
    ///  simply result in an empty or incorrect result.</returns>
    function Range(const AStart, AEnd: NativeInt): IEnexCollection<T>;
    ///  <summary>Selects only a given amount of elements.</summary>
    ///  <param name="ACount">The number of elements to select.</param>
    ///  <returns>A new collection that contains only the first <paramref name="ACount"/> elements.</returns>
    ///  <exception cref="SysUtils|EArgumentOutOfRangeException"><paramref name="ACount"/> is zero.</exception>
    function Take(const ACount: NativeInt): IEnexCollection<T>;
    ///  <summary>Selects all the elements from the collection while a given rule is satisfied.</summary>
    ///  <param name="APredicate">The rule to satisfy.</param>
    ///  <returns>A new collection that contains the selected elements.</returns>
    ///  <remarks>This method selects all elements from the collection while the given rule is satisfied.</remarks>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="APredicate"/> is <c>nil</c>.</exception>
    function TakeWhile(const APredicate: TFunc<T, Boolean>): IEnexCollection<T>;
    ///  <summary>Selects all the elements from the collection while elements are lower than a given value.</summary>
    ///  <param name="ABound">The value to check against.</param>
    ///  <returns>A new collection that contains the selected elements.</returns>
    ///  <remarks>This method selects all elements from the collection while the given rule is satisfied.</remarks>
    function TakeWhileLower(const ABound: T): IEnexCollection<T>;
    ///  <summary>Selects all the elements from the collection while elements are lower than
    ///  or equal to a given value.</summary>
    ///  <param name="ABound">The value to check against.</param>
    ///  <returns>A new collection that contains the selected elements.</returns>
    ///  <remarks>This method selects all elements from the collection while the given rule is satisfied.</remarks>
    function TakeWhileLowerOrEqual(const ABound: T): IEnexCollection<T>;
    ///  <summary>Selects all the elements from the collection while elements are greater than
    ///  a given value.</summary>
    ///  <param name="ABound">The value to check against.</param>
    ///  <returns>A new collection that contains the selected elements.</returns>
    ///  <remarks>This method selects all elements from the collection while the given rule is satisfied.</remarks>
    function TakeWhileGreater(const ABound: T): IEnexCollection<T>;
    ///  <summary>Selects all the elements from the collection while elements are greater than
    ///  or equal to a given value.</summary>
    ///  <param name="ABound">The value to check against.</param>
    ///  <returns>A new collection that contains the selected elements.</returns>
    ///  <remarks>This method selects all elements from the collection while the given rule is satisfied.</remarks>
    function TakeWhileGreaterOrEqual(const ABound: T): IEnexCollection<T>;
    ///  <summary>Selects all the elements from the collection while elements are between a given range of values.</summary>
    ///  <param name="ALower">The lower bound.</param>
    ///  <param name="AHigher">The higher bound.</param>
    ///  <returns>A new collection that contains the selected elements.</returns>
    ///  <remarks>This method selects all elements from the collection while the given rule is satisfied.</remarks>
    function TakeWhileBetween(const ALower, AHigher: T): IEnexCollection<T>;
    ///  <summary>Skips a given amount of elements.</summary>
    ///  <param name="ACount">The number of elements to skip.</param>
    ///  <returns>A new collection that contains the elements that were not skipped.</returns>
    ///  <exception cref="SysUtils|EArgumentOutOfRangeException"><paramref name="ACount"/> is zero.</exception>
    function Skip(const ACount: NativeInt): IEnexCollection<T>;
    ///  <summary>Skips all the elements from the collection while a given rule is satisfied.</summary>
    ///  <param name="APredicate">The rule to satisfy.</param>
    ///  <returns>A new collection that contains the elements that were not skipped.</returns>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="APredicate"/> is <c>nil</c>.</exception>
    function SkipWhile(const APredicate: TFunc<T, Boolean>): IEnexCollection<T>;
    ///  <summary>Skips all the elements from the collection while elements are lower than a given value.</summary>
    ///  <param name="ABound">The value to check.</param>
    ///  <returns>A new collection that contains the elements that were not skipped.</returns>
    function SkipWhileLower(const ABound: T): IEnexCollection<T>;
    ///  <summary>Skips all the elements from the collection while elements are lower than or equal to a given value.</summary>
    ///  <param name="ABound">The value to check.</param>
    ///  <returns>A new collection that contains the elements that were not skipped.</returns>
    function SkipWhileLowerOrEqual(const ABound: T): IEnexCollection<T>;
    ///  <summary>Skips all the elements from the collection while elements are greater than a given value.</summary>
    ///  <param name="ABound">The value to check.</param>
    ///  <returns>A new collection that contains the elements that were not skipped.</returns>
    function SkipWhileGreater(const ABound: T): IEnexCollection<T>;
    ///  <summary>Skips all the elements from the collection while elements are greater than or equal to a given value.</summary>
    ///  <param name="ABound">The value to check.</param>
    ///  <returns>A new collection that contains the elements that were not skipped.</returns>
    function SkipWhileGreaterOrEqual(const ABound: T): IEnexCollection<T>;
    ///  <summary>Skips all the elements from the collection while elements are between a given range of values.</summary>
    ///  <param name="ALower">The lower bound.</param>
    ///  <param name="AHigher">The higher bound.</param>
    ///  <returns>A new collection that contains the elements that were not skipped.</returns>
    function SkipWhileBetween(const ALower, AHigher: T): IEnexCollection<T>;
    ///  <summary>Exposes a type that provides extended Enex operations such as "select".</summary>
    ///  <returns>A record that exposes more Enex operations that otherwise would be impossible.</returns>
    function Op: TEnexExtOps<T>;
    ///  <summary>Creates a new list containing the elements of this collection.</summary>
    ///  <returns>A list containing the elements copied from this collection.</returns>
    ///  <remarks>This method also copies the rule set of this collection. Be careful if the rule set
    ///  performs cleanup on the elements.</remarks>
    function ToList(): IList<T>;
    ///  <summary>Creates a new set containing the elements of this collection.</summary>
    ///  <returns>A set containing the elements copied from this collection.</returns>
    ///  <remarks>This method also copies the rule set of this collection. Be careful if the rule set
    ///  performs cleanup on the elements.</remarks>
    function ToSet(): ISet<T>;
    ///  <summary>Compares the elements in this collection to another collection.</summary>
    ///  <param name="AObject">The instance to compare against.</param>
    ///  <returns>An integer value depicting the result of the comparison operation.
    ///  If the result is less than zero, <c>Self</c> is less than <paramref name="AObject"/>. If the result is zero,
    ///  <c>Self</c> is equal to <paramref name="AObject"/>. And finally, if the result is greater than zero, <c>Self</c> is greater
    ///  than <paramref name="AObject"/>.</returns>
    function CompareTo(AObject: TObject): Integer;
    ///  <summary>Generates the hash code of all the elements in the collection.</summary>
    ///  <returns>An integer value representing the hash codes of all the elements in the collection.</returns>
    function GetHashCode(): Integer; override;
    ///  <summary>Checks whether this collection is equal to another collection.</summary>
    ///  <param name="Obj">The collection to check against.</param>
    ///  <returns><c>True</c> if the collections are equal; <c>False</c> otherwise.</returns>
    ///  <remarks>This method checks whether <paramref name="Obj"/> is not <c>nil</c>, and that
    ///  <paramref name="Obj"/> is an Enex collection. Then, elements are checked for equality one by one.</remarks>
    function Equals(Obj: TObject): Boolean; override;
    ///  <summary>Generates a new collection that contains a given value for a given number of times.</summary>
    ///  <param name="AElement">The element to fill the collection with.</param>
    ///  <param name="ACount">The number of times the element is present in the collection (the length of the collection).</param>
    ///  <param name="ARules">The rule set describing the elements in the new collection.</param>
    ///  <returns>A new collection containing the <paramref name="AElement"/>, <paramref name="ACount"/> times.</returns>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="AElement"/> is <c>nil</c>.</exception>
    ///  <exception cref="SysUtils|EArgumentOutOfRangeException"><paramref name="ACount"/> is zero or less.</exception>
    class function Fill(const AElement: T; const ACount: NativeInt; const ARules: TRules<T>): IEnexCollection<T>; overload; static;
    ///  <summary>Generates a new collection that contains a given value for a given number of times.</summary>
    ///  <param name="AElement">The element to fill the collection with.</param>
    ///  <param name="ACount">The number of times the element is present in the collection (the length of the collection).</param>
    ///  <returns>A new collection containing the <paramref name="AElement"/>, <paramref name="ACount"/> times.</returns>
    ///  <exception cref="SysUtils|EArgumentOutOfRangeException"><paramref name="ACount"/> is zero or less.</exception>
    class function Fill(const AElement: T; const ACount: NativeInt): IEnexCollection<T>; overload; static;
  end;
  ///  <summary>Base class for all associative Enex collections.</summary>
  ///  <remarks>All associative Enex collections (ex. dictionary or multi-map) are derived from this base class.
  ///  It implements the extended Enex operations based on enumerability and introduces functional
  ///  serialization support.</remarks>
  TEnexAssociativeCollection<TKey, TValue> = class abstract(TCollection<TPair<TKey, TValue>>,
      IEnexAssociativeCollection<TKey, TValue>)
  private
    FKeyRules: TRules<TKey>;
    FValueRules: TRules<TValue>;
    FKeyRemoveNotification: TRemoveNotification<TKey>;
    FValueRemoveNotification: TRemoveNotification<TValue>;
  protected
    ///  <summary>Specifies a custom remove notification method that will be called by this
    ///  collection when keys are removed.</summary>
    ///  <returns>The notification method.</returns>
    property KeyRemoveNotification: TRemoveNotification<TKey> read FKeyRemoveNotification write FKeyRemoveNotification;
    ///  <summary>Specifies a custom remove notification method that will be called by this
    ///  collection when values are removed.</summary>
    ///  <returns>The notification method.</returns>
    property ValueRemoveNotification: TRemoveNotification<TValue> read FValueRemoveNotification write FValueRemoveNotification;
    ///  <summary>Compares two keys for equality.</summary>
    ///  <param name="ALeft">The first key.</param>
    ///  <param name="ARight">The second key.</param>
    ///  <returns><c>True</c> if the keys are equal; <c>False</c> otherwise.</returns>
    ///  <remarks>This method uses the equality comparer. If such a comparer was not provided
    ///  a default one is requested.</remarks>
    function KeysAreEqual(const ALeft, ARight: TKey): Boolean;
    ///  <summary>Compares two keys.</summary>
    ///  <param name="ALeft">The first key.</param>
    ///  <param name="ARight">The second key.</param>
    ///  <returns>A value less than zero if <paramref name="ALeft"/> is less than <paramref name="ARight"/>.
    ///  A value greater than zero if <paramref name="ALeft"/> is greater than <paramref name="ARight"/>. Zero if
    ///  <paramref name="ALeft"/> is equal to <paramref name="ARight"/>.</returns>
    ///  <remarks>This method uses the comparer. If such a comparer was not provided
    ///  a default one is requested.</remarks>
    function CompareKeys(const ALeft, ARight: TKey): NativeInt;
    ///  <summary>Generates a hash code for the given key.</summary>
    ///  <param name="AValue">The key.</param>
    ///  <returns>The calculated hash code.</returns>
    ///  <remarks>This method uses the equality comparer. If such a comparer was not provided
    ///  a default one is requested.</remarks>
    function GetKeyHashCode(const AValue: TKey): NativeInt; overload;
    ///  <summary>Compares two values for equality.</summary>
    ///  <param name="ALeft">The first value.</param>
    ///  <param name="ARight">The second value.</param>
    ///  <returns><c>True</c> if the keys are equal; <c>False</c> otherwise.</returns>
    ///  <remarks>This method uses the equality comparer. If such a comparer was not provided
    ///  a default one is requested.</remarks>
    function ValuesAreEqual(const ALeft, ARight: TValue): Boolean;
    ///  <summary>Compares two values.</summary>
    ///  <param name="ALeft">The first value.</param>
    ///  <param name="ARight">The second value.</param>
    ///  <returns>A value less than zero if <paramref name="ALeft"/> is less than <paramref name="ARight"/>.
    ///  A value greater than zero if <paramref name="ALeft"/> is greater than <paramref name="ARight"/>. Zero if
    ///  <paramref name="ALeft"/> is equal to <paramref name="ARight"/>.</returns>
    ///  <remarks>This method uses the comparer. If such a comparer was not provided
    ///  a default one is requested.</remarks>
    function CompareValues(const ALeft, ARight: TValue): NativeInt;
    ///  <summary>Generates a hash code for the given value.</summary>
    ///  <param name="AValue">The value.</param>
    ///  <returns>The calculated hash code.</returns>
    ///  <remarks>This method uses the equality comparer. If such a comparer was not provided
    ///  a default one is requested.</remarks>
    function GetValueHashCode(const AValue: TValue): NativeInt; overload;
    ///  <summary>Specifies the rule set that describes the keys of the stored pairs.</summary>
    ///  <returns>A rule set describing the keys.</returns>
    property KeyRules: TRules<TKey> read FKeyRules;
    ///  <summary>Specifies the rule set that describes the values of the stored pairs.</summary>
    ///  <returns>A rule set describing the values.</returns>
    property ValueRules: TRules<TValue> read FValueRules;
    ///  <summary>Override in descendent classed to properly handle keys that are removed from
    ///  the collection.</summary>
    ///  <param name="AKey">The key being removed.</param>
    ///  <remarks>This method is called by the collection when a key is removed and the caller has
    ///  no possibility of obtaining it. For example, a call to <c>Clear</c> calls this method for each key
    ///  of the collection.</remarks>
    procedure HandleKeyRemoved(const AKey: TKey); virtual;
    ///  <summary>Override in descendaet classed to properly handle values that are removed from
    ///  the collection.</summary>
    ///  <param name="AValue">The key being removed.</param>
    ///  <remarks>This method is called by the collection when a value is removed and the caller has
    ///  no possibility of obtaining it. For example, a call to <c>Clear</c> calls this method for each value
    ///  of the collection.</remarks>
    procedure HandleValueRemoved(const AValue: TValue); virtual;
    ///  <summary>Call this method in descendent collections to properly invoke the removal mechanism.</summary>
    ///  <param name="AKey">The key being removed.</param>
    ///  <remarks>This method verifies whether a custom removal notification is registered and calls it. Otherwise the normal
    ///  removal mechanisms are involved.</remarks>
    procedure NotifyKeyRemoved(const AKey: TKey);
    ///  <summary>Call this method in descendent collections to properly invoke the removal mechanism.</summary>
    ///  <param name="AValue">The key being removed.</param>
    ///  <remarks>This method verifies whether a custom removal notification is registered and calls it. Otherwise the normal
    ///  removal mechanisms are involved.</remarks>
    procedure NotifyValueRemoved(const AValue: TValue);
  public
    ///  <summary>Instantiates this class.</summary>
    ///  <remarks>The default comparer and equality comparer are requested if this constructor is used. Do not call this method if
    ///  you don't know what you are doing.</remarks>
    constructor Create(); overload;
    ///  <summary>Instantiates this class.</summary>
    ///  <param name="AKeyRules">The rules set used by the collection for its keys.</param>
    ///  <param name="AValueRules">The rules set used by the collection for its values.</param>
    ///  <remarks>The provided rules set is used by this collection. This constructor must be called from descendent collections.</remarks>
    constructor Create(const AKeyRules: TRules<TKey>; const AValueRules: TRules<TValue>); overload;
    ///  <summary>Returns the value associated with the given key.</summary>
    ///  <param name="AKey">The key for which to return the associated value.</param>
    ///  <returns>The value associated with the given key.</returns>
    ///  <exception cref="Collections.Base|EKeyNotFoundException">No such key in the collection.</exception>
    function ValueForKey(const AKey: TKey): TValue; virtual;
    ///  <summary>Checks whether the collection contains a given key-value pair.</summary>
    ///  <param name="AKey">The key part of the pair.</param>
    ///  <param name="AValue">The value part of the pair.</param>
    ///  <returns><c>True</c> if the given key-value pair exists; <c>False</c> otherwise.</returns>
    function KeyHasValue(const AKey: TKey; const AValue: TValue): Boolean; virtual;
    ///  <summary>Returns the biggest key.</summary>
    ///  <returns>The biggest key stored in the collection.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    function MaxKey(): TKey; virtual;
    ///  <summary>Returns the smallest key.</summary>
    ///  <returns>The smallest key stored in the collection.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    function MinKey(): TKey; virtual;
    ///  <summary>Returns the biggest value.</summary>
    ///  <returns>The biggest value stored in the collection.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    function MaxValue(): TValue; virtual;
    ///  <summary>Returns the smallest value.</summary>
    ///  <returns>The smallest value stored in the collection.</returns>
    ///  <exception cref="Collections.Base|ECollectionEmptyException">The collection is empty.</exception>
    function MinValue(): TValue; virtual;
    ///  <summary>Checks whether this collection includes the key-value pairs in another collection.</summary>
    ///  <param name="ACollection">The collection to check against.</param>
    ///  <returns><c>True</c> if this collection includes the elements in another; <c>False</c> otherwise.</returns>
    function Includes(const ACollection: IEnumerable<TPair<TKey, TValue>>): Boolean; virtual;
    ///  <summary>Returns an Enex collection that contains only the keys.</summary>
    ///  <returns>An Enex collection that contains all the keys stored in the collection.</returns>
    function SelectKeys(): IEnexCollection<TKey>; virtual;
    ///  <summary>Returns an Enex collection that contains only the values.</summary>
    ///  <returns>An Enex collection that contains all the values stored in the collection.</returns>
    function SelectValues(): IEnexCollection<TValue>; virtual;
    ///  <summary>Selects all the key-value pairs from the collection excluding the duplicates by key.</summary>
    ///  <returns>A new collection that contains the distinct pairs.</returns>
    function DistinctByKeys(): IEnexAssociativeCollection<TKey, TValue>;
    ///  <summary>Selects all the key-value pairs from the collection excluding the duplicates by value.</summary>
    ///  <returns>A new collection that contains the distinct pairs.</returns>
    function DistinctByValues(): IEnexAssociativeCollection<TKey, TValue>;
    ///  <summary>Selects only the key-value pairs that satisfy a given rule.</summary>
    ///  <param name="APredicate">The predicate that represents the rule.</param>
    ///  <returns>A new collection that contains only the pairs that satisfy the given rule.</returns>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="APredicate"/> is <c>nil</c>.</exception>
    function Where(const APredicate: TFunc<TKey, TValue, Boolean>): IEnexAssociativeCollection<TKey, TValue>;
    ///  <summary>Selects only the key-value pairs that do not satisfy a given rule.</summary>
    ///  <param name="APredicate">The predicate that represents the rule.</param>
    ///  <returns>A new collection that contains only the pairs that do not satisfy the given rule.</returns>
    ///  <exception cref="SysUtils|EArgumentNilException"><paramref name="APredicate"/> is <c>nil</c>.</exception>
    function WhereNot(const APredicate: TFunc<TKey, TValue, Boolean>): IEnexAssociativeCollection<TKey, TValue>;
    ///  <summary>Selects only the key-value pairs whose keys are less than a given value.</summary>
    ///  <param name="ABound">The value to compare against.</param>
    ///  <returns>A new collection that contains only the pairs that satisfy the relationship.</returns>
    function WhereKeyLower(const ABound: TKey): IEnexAssociativeCollection<TKey, TValue>;
    ///  <summary>Selects only the key-value pairs whose keys are less than or equal to a given value.</summary>
    ///  <param name="ABound">The value to compare against.</param>
    ///  <returns>A new collection that contains only the pairs that satisfy the relationship.</returns>
    function WhereKeyLowerOrEqual(const ABound: TKey): IEnexAssociativeCollection<TKey, TValue>;
    ///  <summary>Selects only the key-value pairs whose keys are greater than a given value.</summary>
    ///  <param name="ABound">The value to compare against.</param>
    ///  <returns>A new collection that contains only the pairs that satisfy the relationship.</returns>
    function WhereKeyGreater(const ABound: TKey): IEnexAssociativeCollection<TKey, TValue>;
    ///  <summary>Selects only the key-value pairs whose keys are greater than or equal to a given value.</summary>
    ///  <param name="ABound">The value to compare against.</param>
    ///  <returns>A new collection that contains only the pairs that satisfy the relationship.</returns>
    function WhereKeyGreaterOrEqual(const ABound: TKey): IEnexAssociativeCollection<TKey, TValue>;
    ///  <summary>Selects only the key-value pairs whose keys are contained whithin a given interval.</summary>
    ///  <param name="ALower">The lower bound.</param>
    ///  <param name="AHigher">The upper bound.</param>
    ///  <returns>A new collection that contains only the pairs that satisfy the relationship.</returns>
    function WhereKeyBetween(const ALower, AHigher: TKey): IEnexAssociativeCollection<TKey, TValue>;
    ///  <summary>Selects only the key-value pairs whose values are less than a given value.</summary>
    ///  <param name="ABound">The value to compare against.</param>
    ///  <returns>A new collection that contains only the pairs that satisfy the relationship.</returns>
    function WhereValueLower(const ABound: TValue): IEnexAssociativeCollection<TKey, TValue>;
    ///  <summary>Selects only the key-value pairs whose values are less than or equal to a given value.</summary>
    ///  <param name="ABound">The value to compare against.</param>
    ///  <returns>A new collection that contains only the pairs that satisfy the relationship.</returns>
    function WhereValueLowerOrEqual(const ABound: TValue): IEnexAssociativeCollection<TKey, TValue>;
    ///  <summary>Selects only the key-value pairs whose values are greater than a given value.</summary>
    ///  <param name="ABound">The value to compare against.</param>
    ///  <returns>A new collection that contains only the pairs that satisfy the relationship.</returns>
    function WhereValueGreater(const ABound: TValue): IEnexAssociativeCollection<TKey, TValue>;
    ///  <summary>Selects only the key-value pairs whose values are greater than or equal to a given value.</summary>
    ///  <param name="ABound">The value to compare against.</param>
    ///  <returns>A new collection that contains only the pairs that satisfy the relationship.</returns>
    function WhereValueGreaterOrEqual(const ABound: TValue): IEnexAssociativeCollection<TKey, TValue>;
    ///  <summary>Selects only the key-value pairs whose values are contained whithin a given interval.</summary>
    ///  <param name="ALower">The lower bound.</param>
    ///  <param name="AHigher">The upper bound.</param>
    ///  <returns>A new collection that contains only the pairs that satisfy the relationship.</returns>
    function WhereValueBetween(const ALower, AHigher: TValue): IEnexAssociativeCollection<TKey, TValue>;
    ///  <summary>Creates a new dictionary containing the elements of this collection.</summary>
    ///  <returns>A dictionary containing the elements copied from this collection.</returns>
    ///  <remarks>This method also copies the rule set of this collection. Be careful if the rule set
    ///  performs cleanup on the elements.</remarks>
    ///  <exception cref="Collections.Base|EDuplicateKeyException">The collection contains more than
    ///  one key-value pair with the same key.</exception>
    function ToDictionary(): IDictionary<TKey, TValue>;
  end;
{$ENDREGION}
{$REGION 'Exception Support'}
type
  ///  <summary>Thrown when an attempt to call an unsupported default parameterless constructor is made.</summary>
  EDefaultConstructorNotAllowed = class(Exception);
  ///  <summary>Thrown when a <see cref="Collections.Base|TRefCountedObject">Collections.Base.TRefCountedObject</see> tries to keep itself alive.</summary>
  ECannotSelfReferenceException = class(Exception);
{$IF RTLVersion < 22}
  ///  <summary>Thrown when a given argument is <c>nil</c>.</summary>
  ///  <remarks>This exception is normally provided by Delphi XE's SysUtils.pas.</remarks>
  EArgumentNilException = class(EArgumentException);
{$IFEND}
  ///  <summary>Thrown when a given argument combination specifies a smaller range than required.</summary>
  ///  <remarks>This exception is usually used by collections. The exception is thrown when there is not enough
  ///  space in an array to copy the values to.</remarks>
  EArgumentOutOfSpaceException = class(EArgumentOutOfRangeException);
  ///  <summary>Represents all exceptions that are thrown when collections are involved.</summary>
  ECollectionException = class(Exception);
  ///  <summary>Thrown when an enumerator detects that the enumerated collection was changed.</summary>
  ECollectionChangedException = class(ECollectionException);
  ///  <summary>Thrown when a collection was identified to be empty (and it shouldn't have been).</summary>
  ECollectionEmptyException = class(ECollectionException);
  ///  <summary>Thrown when a collection was expected to have only one exception.</summary>
  ECollectionNotOneException = class(ECollectionException);
  ///  <summary>Thrown when a predicated applied to a collection generates a void collection.</summary>
  ECollectionFilteredEmptyException = class(ECollectionException);
  ///  <summary>Thrown when trying to add a key-value pair into a collection that already has that key
  ///  in it.</summary>
  EDuplicateKeyException = class(ECollectionException);
  ///  <summary>Thrown when the key (of a pair) is not found in the collection.</summary>
  EKeyNotFoundException = class(ECollectionException);
  ///  <summary>A static class that offers methods for throwing exceptions.</summary>
  ///  <remarks><see cref="Collections.Base|ExceptionHelper">Collections.Base.ExceptionHelper</see> is used internally in this package to
  ///  throw all kinds of exceptions. This class is useful because it separates the exceptions
  ///  (including the messages) from the rest of the code.</remarks>
  ExceptionHelper = class sealed
  public
    ///  <summary>Internal method. Do not call directly!</summary>
    ///  <remarks>The interface of this function may change in the future.</remarks>
    class procedure Throw_CannotSelfReferenceError();
    ///  <summary>Internal method. Do not call directly!</summary>
    ///  <remarks>The interface of this function may change in the future.</remarks>
    class procedure Throw_ArgumentNilError(const ArgName: String);
    ///  <summary>Internal method. Do not call directly!</summary>
    ///  <remarks>The interface of this function may change in the future.</remarks>
    class procedure Throw_ArgumentOutOfRangeError(const ArgName: String);
    ///  <summary>Internal method. Do not call directly!</summary>
    ///  <remarks>The interface of this function may change in the future.</remarks>
    class procedure Throw_ArgumentOutOfSpaceError(const ArgName: String);
    ///  <summary>Internal method. Do not call directly!</summary>
    ///  <remarks>The interface of this function may change in the future.</remarks>
    class procedure Throw_CollectionChangedError();
    ///  <summary>Internal method. Do not call directly!</summary>
    ///  <remarks>The interface of this function may change in the future.</remarks>
    class procedure Throw_CollectionEmptyError();
    ///  <summary>Internal method. Do not call directly!</summary>
    ///  <remarks>The interface of this function may change in the future.</remarks>
    class procedure Throw_CollectionHasMoreThanOneElement();
    ///  <summary>Internal method. Do not call directly!</summary>
    ///  <remarks>The interface of this function may change in the future.</remarks>
    class procedure Throw_CollectionHasNoFilteredElements();
    ///  <summary>Internal method. Do not call directly!</summary>
    ///  <remarks>The interface of this function may change in the future.</remarks>
    class procedure Throw_DuplicateKeyError(const ArgName: String);
    ///  <summary>Internal method. Do not call directly!</summary>
    ///  <remarks>The interface of this function may change in the future.</remarks>
    class procedure Throw_KeyNotFoundError(const ArgName: String);
    ///  <summary>Internal method. Do not call directly!</summary>
    ///  <remarks>The interface of this function may change in the future.</remarks>
    class procedure Throw_TypeNotAClassError(const TypeName: String);
    ///  <summary>Internal method. Do not call directly!</summary>
    ///  <remarks>The interface of this function may change in the future.</remarks>
    class procedure Throw_TypeDoesNotExposeMember(const MemberName: String);
  end;
resourcestring
  SDefaultParameterlessCtorNotAllowed = 'Default parameterless constructor not allowed!';
  SCannotSelfReference = 'The object cannot self-reference!';
  SNilArgument = 'Argument "%s" is nil. Expected a normal non-disposed object!';
  SOutOfRangeArgument = 'Argument "%s" is out of range. An argument that falls into the required range of values is expected!';
  SOutOfSpaceArgument = 'Argument "%s" does not have enough space to hold the result!';
  SParentCollectionChanged = 'Parent collection has changed. Cannot continue the operation!';
  SKeyNotFound = 'The key given by the "%s" argument was not found in the collection!';
  SDuplicateKey = 'The key given by the "%s" argument was already registered in the collection!';
  SEmptyCollection = 'The collection is empty! The operation cannot be performed!';
  SCollectionHasMoreThanOneElements = 'The collection has more than one element!';
  SCollectionHasNoFilteredElements = 'The applied predicate generates a void collection.';
  STypeNotAClass = 'The type "%s" on which the operation was invoked is not a class!';
  STypeDoesNotExposeMember = 'The type the collection operates on does not expose member "%s"!';
{$ENDREGION}
{$REGION 'Enex Internal Enumerables'}
  //TODO: doc all these classes :(
type
  { The "Where" collection }
  TEnexWhereCollection<T> = class sealed(TEnexCollection<T>)
  private
  type
    { The "Where" enumerator }
    TEnumerator = class(TEnumerator<T>)
    private
      FCollection: TEnexWhereCollection<T>;
      FEnumerator: IEnumerator<T>;
    public
      { Constructor }
      constructor Create(const ACollection: TEnexWhereCollection<T>);
      { Destructor }
      destructor Destroy(); override;
      function GetCurrent(): T; override;
      function MoveNext(): Boolean; override;
    end;
  var
    FCollection: TEnexCollection<T>;
    FPredicate: TFunc<T, Boolean>;
    FInvertResult: Boolean;
  public
    { Constructors }
    constructor Create(const ACollection: TEnexCollection<T>;
      const APredicate: TFunc<T, Boolean>; const AInvertResult: Boolean); overload;
    { Destructor }
    destructor Destroy(); override;
    { IEnumerable<T> }
    function GetEnumerator(): IEnumerator<T>; override;
  end;
  { The "Select" collection }
  TEnexSelectCollection<T, TOut> = class sealed(TEnexCollection<TOut>, IEnexCollection<TOut>)
  private
  type
    { The "Select" enumerator }
    TEnumerator = class(TEnumerator<TOut>)
    private
      FCollection: TEnexSelectCollection<T, TOut>;
      FEnumerator: IEnumerator<T>;
      FCurrent: TOut;
    public
      { Constructor }
      constructor Create(const ACollection: TEnexSelectCollection<T, TOut>);
      { Destructor }
      destructor Destroy(); override;
      function GetCurrent(): TOut; override;
      function MoveNext(): Boolean; override;
    end;
  var
    FCollection: TEnexCollection<T>;
    FSelector: TFunc<T, TOut>;
  protected
    { Enex: Defaults }
    function GetCount(): NativeInt; override;
  public
    { Constructors }
    constructor Create(const ACollection: TEnexCollection<T>; const ASelector: TFunc<T, TOut>; const ARules: TRules<TOut>); overload;
    { Destructor }
    destructor Destroy(); override;
    { IEnumerable<T> }
    function GetEnumerator(): IEnumerator<TOut>; override;
    { Enex Overrides }
    function Empty(): Boolean; override;
    function First(): TOut; override;
    function Last(): TOut; override;
    function Single(): TOut; override;
    function ElementAt(const AIndex: NativeInt): TOut; override;
  end;
  { The "Select Class" collection }
  TEnexSelectClassCollection<T, TOut: class> = class sealed(TEnexCollection<TOut>, IEnexCollection<TOut>)
  private
  type
    { The "Select Class" enumerator }
    TEnumerator = class(TEnumerator<TOut>)
    private
      FCollection: TEnexSelectClassCollection<T, TOut>;
      FEnumerator: IEnumerator<T>;
      FCurrent: TOut;
    public
      { Constructor }
      constructor Create(const ACollection: TEnexSelectClassCollection<T, TOut>);
      { Destructor }
      destructor Destroy(); override;
      function GetCurrent(): TOut; override;
      function MoveNext(): Boolean; override;
    end;
  var
    FCollection: TEnexCollection<T>;
  public
    { Constructors }
    constructor Create(const ACollection: TEnexCollection<T>; const ARules: TRules<TOut>); overload;
    { Destructor }
    destructor Destroy(); override;
    { IEnumerable<T> }
    function GetEnumerator(): IEnumerator<TOut>; override;
  end;
  { The "Concatenation" collection }
  TEnexConcatCollection<T> = class sealed(TEnexCollection<T>)
  private
  type
    { The "Concatenation" enumerator }
    TEnumerator = class(TEnumerator<T>)
    private
      FCollection: TEnexConcatCollection<T>;
      FEnumerator1, FEnumerator2: IEnumerator<T>;
    public
      { Constructor }
      constructor Create(const ACollection: TEnexConcatCollection<T>);
      { Destructor }
      destructor Destroy(); override;
      function GetCurrent(): T; override;
      function MoveNext(): Boolean; override;
    end;
  var
    FCollection1: TEnexCollection<T>;
    FCollection2: IEnexCollection<T>;
  protected
    { ICollection support/hidden }
    function GetCount(): NativeInt; override;
  public
    { Constructors }
    constructor Create(const ACollection1: TEnexCollection<T>; const ACollection2: IEnexCollection<T>); overload;
    { Destructor }
    destructor Destroy(); override;
    { IEnumerable<T> }
    function GetEnumerator(): IEnumerator<T>; override;
    { Enex Overrides }
    function Empty(): Boolean; override;
    function Any(const APredicate: TFunc<T, Boolean>): Boolean; override;
    function All(const APredicate: TFunc<T, Boolean>): Boolean; override;
  end;
  { The "Union" collection }
  TEnexUnionCollection<T> = class sealed(TEnexCollection<T>)
  private
  type
    { The "Union" enumerator }
    TEnumerator = class(TEnumerator<T>)
    private
      FCollection: TEnexUnionCollection<T>;
      FEnumerator1, FEnumerator2: IEnumerator<T>;
      FSet: ISet<T>;
    public
      { Constructor }
      constructor Create(const ACollection: TEnexUnionCollection<T>);
      { Destructor }
      destructor Destroy(); override;
      function GetCurrent(): T; override;
      function MoveNext(): Boolean; override;
    end;
  var
    FCollection1: TEnexCollection<T>;
    FCollection2: IEnexCollection<T>;
  public
    { Constructors }
    constructor Create(const ACollection1: TEnexCollection<T>; const ACollection2: IEnexCollection<T>); overload;
    { Destructor }
    destructor Destroy(); override;
    { IEnumerable<T> }
    function GetEnumerator(): IEnumerator<T>; override;
  end;
  { The "Exclusion" collection }
  TEnexExclusionCollection<T> = class sealed(TEnexCollection<T>)
  private
  type
    { The "Exclusion" enumerator }
    TEnumerator = class(TEnumerator<T>)
    private
      FCollection: TEnexExclusionCollection<T>;
      FEnumerator: IEnumerator<T>;
      FSet: ISet<T>;
    public
      { Constructor }
      constructor Create(const ACollection: TEnexExclusionCollection<T>);
      { Destructor }
      destructor Destroy(); override;
      function GetCurrent(): T; override;
      function MoveNext(): Boolean; override;
    end;
  var
    FCollection1: TEnexCollection<T>;
    FCollection2: IEnexCollection<T>;
  public
    { Constructors }
    constructor Create(const ACollection1: TEnexCollection<T>; const ACollection2: IEnexCollection<T>); overload;
    { Destructor }
    destructor Destroy(); override;
    { IEnumerable<T> }
    function GetEnumerator(): IEnumerator<T>; override;
  end;
  { The "Intersection" collection }
  TEnexIntersectionCollection<T> = class sealed(TEnexCollection<T>)
  private
  type
    { The "Intersection" enumerator }
    TEnumerator = class(TEnumerator<T>)
    private
      FCollection: TEnexIntersectionCollection<T>;
      FEnumerator: IEnumerator<T>;
      FSet: ISet<T>;
    public
      { Constructor }
      constructor Create(const ACollection: TEnexIntersectionCollection<T>);
      { Destructor }
      destructor Destroy(); override;
      function GetCurrent(): T; override;
      function MoveNext(): Boolean; override;
    end;
  var
    FCollection1: TEnexCollection<T>;
    FCollection2: IEnexCollection<T>;
  public
    { Constructors }
    constructor Create(const ACollection1: TEnexCollection<T>; const ACollection2: IEnexCollection<T>); overload;
    { Destructor }
    destructor Destroy(); override;
    { IEnumerable<T> }
    function GetEnumerator(): IEnumerator<T>; override;
  end;
  { The "Distinct" collection }
  TEnexDistinctCollection<T> = class sealed(TEnexCollection<T>)
  private
  type
    { The "Distinct" enumerator }
    TEnumerator = class(TEnumerator<T>)
    private
      FCollection: TEnexDistinctCollection<T>;
      FEnumerator: IEnumerator<T>;
      FSet: ISet<T>;
    public
      { Constructor }
      constructor Create(const ACollection: TEnexDistinctCollection<T>);
      { Destructor }
      destructor Destroy(); override;
      function GetCurrent(): T; override;
      function MoveNext(): Boolean; override;
    end;
  var
    FCollection: TEnexCollection<T>;
  public
    { Constructors }
    constructor Create(const ACollection: TEnexCollection<T>); overload;
    { Destructor }
    destructor Destroy(); override;
    { IEnumerable<T> }
    function GetEnumerator(): IEnumerator<T>; override;
  end;
  { The "Range" collection }
  TEnexRangeCollection<T> = class sealed(TEnexCollection<T>)
  private
  type
    { The "Range" enumerator }
    TEnumerator = class(TEnumerator<T>)
    private
      FCollection: TEnexRangeCollection<T>;
      FEnumerator: IEnumerator<T>;
      FIdx: NativeInt;
    public
      { Constructor }
      constructor Create(const ACollection: TEnexRangeCollection<T>);
      { Destructor }
      destructor Destroy(); override;
      function GetCurrent(): T; override;
      function MoveNext(): Boolean; override;
    end;
  var
    FStart, FEnd: NativeInt;
    FCollection: TEnexCollection<T>;
  public
    { Constructors }
    constructor Create(const ACollection: TEnexCollection<T>; const AStart, AEnd: NativeInt); overload;
    { Destructor }
    destructor Destroy(); override;
    { IEnumerable<T> }
    function GetEnumerator(): IEnumerator<T>; override;
  end;
  { The "Skip" collection }
  TEnexSkipCollection<T> = class sealed(TEnexCollection<T>)
  private
  type
    { The "Skip" enumerator }
    TEnumerator = class(TEnumerator<T>)
    private
      FCollection: TEnexSkipCollection<T>;
      FEnumerator: IEnumerator<T>;
      FIdx: NativeInt;
    public
      { Constructor }
      constructor Create(const ACollection: TEnexSkipCollection<T>);
      { Destructor }
      destructor Destroy(); override;
      function GetCurrent(): T; override;
      function MoveNext(): Boolean; override;
    end;
  var
    FCount: NativeInt;
    FCollection: TEnexCollection<T>;
  public
    { Constructors }
    constructor Create(const ACollection: TEnexCollection<T>; const ACount: NativeInt); overload;
    { Destructor }
    destructor Destroy(); override;
    { IEnumerable<T> }
    function GetEnumerator(): IEnumerator<T>; override;
  end;
  { The "Take" collection }
  TEnexTakeCollection<T> = class sealed(TEnexCollection<T>)
  private
  type
    { The "Take" enumerator }
    TEnumerator = class(TEnumerator<T>)
    private
      FCollection: TEnexTakeCollection<T>;
      FEnumerator: IEnumerator<T>;
      FIdx: NativeInt;
    public
      { Constructor }
      constructor Create(const ACollection: TEnexTakeCollection<T>);
      { Destructor }
      destructor Destroy(); override;
      function GetCurrent(): T; override;
      function MoveNext(): Boolean; override;
    end;
  var
    FCount: NativeInt;
    FCollection: TEnexCollection<T>;
  public
    { Constructors }
    constructor Create(const ACollection: TEnexCollection<T>; const ACount: NativeInt); overload;
    { Destructor }
    destructor Destroy(); override;
    { IEnumerable<T> }
    function GetEnumerator(): IEnumerator<T>; override;
  end;
  { The "Fill" collection }
  TEnexFillCollection<T> = class sealed(TEnexCollection<T>)
  private
  type
    { The "Fill" enumerator }
    TEnumerator = class(TEnumerator<T>)
    private
      FCollection: TEnexFillCollection<T>;
      FCount: NativeInt;
    public
      { Constructor }
      constructor Create(const ACollection: TEnexFillCollection<T>);
      { Destructor }
      destructor Destroy(); override;
      function GetCurrent(): T; override;
      function MoveNext(): Boolean; override;
    end;
  var
    FElement: T;
    FCount: NativeInt;
  protected
    { Enex: Defaults }
    function GetCount(): NativeInt; override;
  public
    { Constructors }
    constructor Create(const AElement: T; const ACount: NativeInt; const ARules: TRules<T>);
    { IEnumerable<T> }
    function GetEnumerator(): IEnumerator<T>; override;
    { Enex Overrides }
    function Empty(): Boolean; override;
    function Max(): T; override;
    function Min(): T; override;
    function First(): T; override;
    function FirstOrDefault(const ADefault: T): T; override;
    function Last(): T; override;
    function LastOrDefault(const ADefault: T): T; override;
    function Single(): T; override;
    function SingleOrDefault(const ADefault: T): T; override;
    function Aggregate(const AAggregator: TFunc<T, T, T>): T; override;
    function AggregateOrDefault(const AAggregator: TFunc<T, T, T>; const ADefault: T): T; override;
    function ElementAt(const AIndex: NativeInt): T; override;
    function ElementAtOrDefault(const AIndex: NativeInt; const ADefault: T): T; override;
    function Any(const APredicate: TFunc<T, Boolean>): Boolean; override;
    function All(const APredicate: TFunc<T, Boolean>): Boolean; override;
    function EqualsTo(const ACollection: IEnumerable<T>): Boolean; override;
  end;
  { The "Take While" collection }
  TEnexTakeWhileCollection<T> = class sealed(TEnexCollection<T>)
  private
  type
    { The "Take While" enumerator }
    TEnumerator = class(TEnumerator<T>)
    private
      FCollection: TEnexTakeWhileCollection<T>;
      FEnumerator: IEnumerator<T>;
    public
      { Constructor }
      constructor Create(const ACollection: TEnexTakeWhileCollection<T>);
      { Destructor }
      destructor Destroy(); override;
      function GetCurrent(): T; override;
      function MoveNext(): Boolean; override;
    end;
  var
    FCollection: TEnexCollection<T>;
    FPredicate: TFunc<T, Boolean>;
  public
    { Constructors }
    constructor Create(const ACollection: TEnexCollection<T>; const APredicate: TFunc<T, Boolean>); overload;
    { Destructor }
    destructor Destroy(); override;
    { IEnumerable<T> }
    function GetEnumerator(): IEnumerator<T>; override;
  end;
  { The "Skip While" collection }
  TEnexSkipWhileCollection<T> = class sealed(TEnexCollection<T>)
  private
  type
    { The "Skip While" enumerator }
    TEnumerator = class(TEnumerator<T>)
    private
      FCollection: TEnexSkipWhileCollection<T>;
      FEnumerator: IEnumerator<T>;
      FStop: Boolean;
    public
      { Constructor }
      constructor Create(const ACollection: TEnexSkipWhileCollection<T>);
      { Destructor }
      destructor Destroy(); override;
      function GetCurrent(): T; override;
      function MoveNext(): Boolean; override;
    end;
  var
    FCollection: TEnexCollection<T>;
    FPredicate: TFunc<T, Boolean>;
  public
    { Constructors }
    constructor Create(const ACollection: TEnexCollection<T>; const APredicate: TFunc<T, Boolean>); overload;
    { Destructor }
    destructor Destroy(); override;
    { IEnumerable<T> }
    function GetEnumerator(): IEnumerator<T>; override;
  end;
  { The "Group By" collection }
  TEnexGroupByCollection<T, TBy> = class sealed(TEnexCollection<IEnexGroupingCollection<TBy, T>>)
  private type
    TEnexGroupingCollection = class(TEnexCollection<T>, IEnexGroupingCollection<TBy, T>)
    private
      FBy: TBy;
      FList: IList<T>;
    public
      function GetKey(): TBy;
      function GetCount(): NativeInt; override;
      function GetEnumerator(): IEnumerator<T>; override;
      procedure CopyTo(var AArray: array of T; const AStartIndex: NativeInt); overload; override;
      function Empty(): Boolean; override;
      function Max(): T; override;
      function Min(): T; override;
      function First(): T; override;
      function FirstOrDefault(const ADefault: T): T; override;
      function Last(): T; override;
      function LastOrDefault(const ADefault: T): T; override;
      function Single(): T; override;
      function SingleOrDefault(const ADefault: T): T; override;
      function Aggregate(const AAggregator: TFunc<T, T, T>): T; override;
      function AggregateOrDefault(const AAggregator: TFunc<T, T, T>; const ADefault: T): T; override;
      function ElementAt(const AIndex: NativeInt): T; override;
      function ElementAtOrDefault(const AIndex: NativeInt; const ADefault: T): T; override;
      function Any(const APredicate: TFunc<T, Boolean>): Boolean; override;
      function All(const APredicate: TFunc<T, Boolean>): Boolean; override;
      function EqualsTo(const ACollection: IEnumerable<T>): Boolean; override;
    end;
  private var
    FCollection: TEnexCollection<T>;
    FSelector: TFunc<T, TBy>;
  public
    { Constructors }
    constructor Create(const ACollection: TEnexCollection<T>; const ASelector: TFunc<T, TBy>);
    { Destructor }
    destructor Destroy(); override;
    { IEnumerable<T> }
    function GetEnumerator(): IEnumerator<IEnexGroupingCollection<TBy, T>>; override;
  end;
  { The "Select Keys" collection }
  TEnexSelectKeysCollection<TKey, TValue> = class sealed(TEnexCollection<TKey>)
  private
  type
    { The "Select Keys" enumerator }
    TEnumerator = class(TEnumerator<TKey>)
    private
      FCollection: TEnexSelectKeysCollection<TKey, TValue>;
      FEnumerator: IEnumerator<TPair<TKey, TValue>>;
      FCurrent: TKey;
    public
      { Constructor }
      constructor Create(const ACollection: TEnexSelectKeysCollection<TKey, TValue>);
      { Destructor }
      destructor Destroy(); override;
      function GetCurrent(): TKey; override;
      function MoveNext(): Boolean; override;
    end;
  var
    FCollection: TEnexAssociativeCollection<TKey, TValue>;
  protected
    { Enex: Defaults }
    function GetCount(): NativeInt; override;
  public
    { Constructors }
    constructor Create(const ACollection: TEnexAssociativeCollection<TKey, TValue>); overload;
    { Destructor }
    destructor Destroy(); override;
    { IEnumerable<T> }
    function GetEnumerator(): IEnumerator<TKey>; override;
  end;
  { The "Select Values" collection }
  TEnexSelectValuesCollection<TKey, TValue> = class sealed(TEnexCollection<TValue>)
  private
  type
    { The "Select Keys" enumerator }
    TEnumerator = class(TEnumerator<TValue>)
    private
      FCollection: TEnexSelectValuesCollection<TKey, TValue>;
      FEnumerator: IEnumerator<TPair<TKey, TValue>>;
      FCurrent: TValue;
    public
      { Constructor }
      constructor Create(const ACollection: TEnexSelectValuesCollection<TKey, TValue>);
      { Destructor }
      destructor Destroy(); override;
      function GetCurrent(): TValue; override;
      function MoveNext(): Boolean; override;
    end;
  var
    FCollection: TEnexAssociativeCollection<TKey, TValue>;
  protected
    { Enex: Defaults }
    function GetCount(): NativeInt; override;
  public
    { Constructors }
    constructor Create(const ACollection: TEnexAssociativeCollection<TKey, TValue>); overload;
    { Destructor }
    destructor Destroy(); override;
    { IEnumerable<T> }
    function GetEnumerator(): IEnumerator<TValue>; override;
  end;
  { The "Where" associative collection }
  TEnexAssociativeWhereCollection<TKey, TValue> = class sealed(TEnexAssociativeCollection<TKey, TValue>,
      IEnexAssociativeCollection<TKey, TValue>)
  private
  type
    { The "Where" associative enumerator }
    TEnumerator = class(TEnumerator<TPair<TKey, TValue>>)
    private
      FCollection: TEnexAssociativeWhereCollection<TKey, TValue>;
      FEnumerator: IEnumerator<TPair<TKey, TValue>>;
    public
      { Constructor }
      constructor Create(const ACollection: TEnexAssociativeWhereCollection<TKey, TValue>);
      { Destructor }
      destructor Destroy(); override;
      function GetCurrent(): TPair<TKey, TValue>; override;
      function MoveNext(): Boolean; override;
    end;
  var
    FCollection: TEnexAssociativeCollection<TKey, TValue>;
    FPredicate: TFunc<TKey, TValue, Boolean>;
    FInvertResult: Boolean;
  public
    { Constructors }
    constructor Create(const ACollection: TEnexAssociativeCollection<TKey, TValue>;
        const APredicate: TFunc<TKey, TValue, Boolean>; const AInvertResult: Boolean); overload;
    { Destructor }
    destructor Destroy(); override;
    { IEnumerable<T> }
    function GetEnumerator(): IEnumerator<TPair<TKey, TValue>>; override;
  end;
  { The "Distinct By Keys" associative collection }
  TEnexAssociativeDistinctByKeysCollection<TKey, TValue> = class sealed(TEnexAssociativeCollection<TKey, TValue>)
  private
  type
    { The "Distinct By Keys" associative enumerator }
    TEnumerator = class(TEnumerator<TPair<TKey, TValue>>)
    private
      FCollection: TEnexAssociativeDistinctByKeysCollection<TKey, TValue>;
      FEnumerator: IEnumerator<TPair<TKey, TValue>>;
      FSet: ISet<TKey>;
    public
      { Constructor }
      constructor Create(const ACollection: TEnexAssociativeDistinctByKeysCollection<TKey, TValue>);
      { Destructor }
      destructor Destroy(); override;
      function GetCurrent(): TPair<TKey, TValue>; override;
      function MoveNext(): Boolean; override;
    end;
  var
    FCollection: TEnexAssociativeCollection<TKey, TValue>;
  public
    { Constructors }
    constructor Create(const ACollection: TEnexAssociativeCollection<TKey, TValue>); overload;
    { Destructor }
    destructor Destroy(); override;
    { IEnumerable<T> }
    function GetEnumerator(): IEnumerator<TPair<TKey, TValue>>; override;
  end;
  { The "Distinct By Values" associative collection }
  TEnexAssociativeDistinctByValuesCollection<TKey, TValue> = class sealed(TEnexAssociativeCollection<TKey, TValue>)
  private
  type
    { The "Distinct By Keys" associative enumerator }
    TEnumerator = class(TEnumerator<TPair<TKey, TValue>>)
    private
      FCollection: TEnexAssociativeDistinctByValuesCollection<TKey, TValue>;
      FEnumerator: IEnumerator<TPair<TKey, TValue>>;
      FSet: ISet<TValue>;
    public
      { Constructor }
      constructor Create(const ACollection: TEnexAssociativeDistinctByValuesCollection<TKey, TValue>);
      { Destructor }
      destructor Destroy(); override;
      function GetCurrent(): TPair<TKey, TValue>; override;
      function MoveNext(): Boolean; override;
    end;
  var
    FCollection: TEnexAssociativeCollection<TKey, TValue>;
  public
    { Constructors }
    constructor Create(const ACollection: TEnexAssociativeCollection<TKey, TValue>); overload;
    { Destructor }
    destructor Destroy(); override;
    { IEnumerable<T> }
    function GetEnumerator(): IEnumerator<TPair<TKey, TValue>>; override;
  end;
{$ENDREGION}
implementation
uses
  TypInfo,
  Collections.Sets,
  Collections.Lists,
  Collections.Dictionaries;
{ TEnexExtOps<T> }
function TEnexExtOps<T>.GroupBy<TKey>(const ASelector: TFunc<T, TKey>): IEnexCollection<IEnexGroupingCollection<TKey, T>>;
begin
  { Check arguments }
  if not Assigned(ASelector) then
    ExceptionHelper.Throw_ArgumentNilError('ASelector');
  { Create an intermediate collection that will lazy-create the actual stuff }
  Result := TEnexGroupByCollection<T, TKey>.Create(FInstance, ASelector);
end;
function TEnexExtOps<T>.Select(const AMemberName: string): IEnexCollection<TAny>;
var
  LSelector: TFunc<T, TAny>;
begin
  { Get selector }
  LSelector := Member.Name<T>(AMemberName);
  if not Assigned(LSelector) then
    ExceptionHelper.Throw_TypeDoesNotExposeMember('AMemberName');
  { Select the member by a name, as out type }
  Result := Select<TAny>(LSelector);
end;
function TEnexExtOps<T>.Select<TOut>(const AMemberName: string): IEnexCollection<TOut>;
var
  LSelector: TFunc<T, TOut>;
begin
  { Get selector }
  LSelector := Member.Name<T, TOut>(AMemberName);
  if not Assigned(LSelector) then
    ExceptionHelper.Throw_TypeDoesNotExposeMember(AMemberName);
  { Select the member by a name, as out type }
  Result := Select<TOut>(LSelector);
end;
function TEnexExtOps<T>.Select(const AMemberNames: array of string): IEnexCollection<TView>;
var
  LSelector: TFunc<T, TView>;
begin
  { Get selector }
  LSelector := Member.Name<T>(AMemberNames);
  if not Assigned(LSelector) then
    ExceptionHelper.Throw_TypeDoesNotExposeMember('...');
  { Select the member by a name, as out type }
  Result := Select<TView>(LSelector);
end;
function TEnexExtOps<T>.Select<TOut>: IEnexCollection<TOut>;
var
  LTypeInfo: PTypeInfo;
begin
  { Make sure that T is a class }
  LTypeInfo := TypeInfo(T);
  { TADA! }
  if (not Assigned(LTypeInfo)) or (LTypeInfo^.Kind <> tkClass) then
    ExceptionHelper.Throw_TypeNotAClassError(GetTypeName(LTypeInfo));
  Result := TEnexSelectClassCollection<TObject, TOut>.Create(FInstance, TRules<TOut>.Default);
end;
function TEnexExtOps<T>.Select<TOut>(const ASelector: TFunc<T, TOut>): IEnexCollection<TOut>;
begin
  { With default type support }
  Result := Select<TOut>(ASelector, TRules<TOut>.Default);
end;
function TEnexExtOps<T>.Select<TOut>(const ASelector: TFunc<T, TOut>; const ARules: TRules<TOut>): IEnexCollection<TOut>;
begin
  { Check arguments }
  if not Assigned(ASelector) then
    ExceptionHelper.Throw_ArgumentNilError('ASelector');
  { Create a new Enex collection }
  Result := TEnexSelectCollection<T, TOut>.Create(FInstance, ASelector, ARules);
end;
{ TCollection<T> }
procedure TCollection<T>.CopyTo(var AArray: array of T);
begin
  { Call upper version }
  CopyTo(AArray, 0);
end;
procedure TCollection<T>.CopyTo(var AArray: array of T; const AStartIndex: NativeInt);
var
  LEnumerator: IEnumerator<T>;
  L, I: NativeInt;
begin
  if (AStartIndex >= Length(AArray)) or (AStartIndex < 0) then
    ExceptionHelper.Throw_ArgumentOutOfRangeError('AStartIndex');
  { Retrieve the enumerator object }
  LEnumerator := GetEnumerator();
  L := Length(AArray);
  I := AStartIndex;
  { Iterate until ANY element supports the predicate }
  while LEnumerator.MoveNext() do
  begin
    if I >= L then
      ExceptionHelper.Throw_ArgumentOutOfSpaceError('AArray/AStartIndex');
    AArray[I] := LEnumerator.Current;
    Inc(I);
  end;
end;
function TCollection<T>.Empty: Boolean;
var
  LEnumerator: IEnumerator<T>;
begin
  { Retrieve the enumerator object }
  LEnumerator := GetEnumerator();
  { Check if empty }
  Result := (not LEnumerator.MoveNext());
end;
function TCollection<T>.GetCount: NativeInt;
var
  LEnumerator: IEnumerator<T>;
begin
  { Retrieve the enumerator object }
  LEnumerator := GetEnumerator();
  { Iterate till the end }
  Result := 0;
  while LEnumerator.MoveNext() do Inc(Result);
end;
function TCollection<T>.Single: T;
var
  LEnumerator: IEnumerator<T>;
begin
  { Retrieve the enumerator object }
  LEnumerator := GetEnumerator();
  { Get the first object in the enumeration, otherwise fail! }
  if LEnumerator.MoveNext() then
    Result := LEnumerator.Current
  else
    ExceptionHelper.Throw_CollectionEmptyError();
  { Fail if more than one elements are there }
  if LEnumerator.MoveNext() then
    ExceptionHelper.Throw_CollectionHasMoreThanOneElement();
end;
function TCollection<T>.SingleOrDefault(const ADefault: T): T;
var
  LEnumerator: IEnumerator<T>;
begin
  { Retrieve the enumerator object }
  LEnumerator := GetEnumerator();
  { Get the first object in the enumeration, otherwise fail! }
  if LEnumerator.MoveNext() then
    Result := LEnumerator.Current
  else
    Exit(ADefault);
  { Fail if more than one elements are there }
  if LEnumerator.MoveNext() then
    ExceptionHelper.Throw_CollectionHasMoreThanOneElement();
end;
function TCollection<T>.ToArray: TArray<T>;
var
  LCount: NativeInt;
  LResult: TArray<T>;
begin
  LCount := Count;
  if LCount > 0 then
  begin
    { Set the length of array }
    SetLength(LResult, LCount);
    { Copy all elements to array }
    CopyTo(LResult);
  end else
    SetLength(LResult, 0);
  Result := LResult;
end;
{ TEnexCollection<T> }
function TEnexCollection<T>.Aggregate(const AAggregator: TFunc<T, T, T>): T;
var
  LEnumerator: IEnumerator<T>;
begin
  if not Assigned(AAggregator) then
    ExceptionHelper.Throw_ArgumentNilError('AAggregator');
  { Retrieve the enumerator object and type }
  LEnumerator := GetEnumerator();
  { Get the first object in the enumeration, otherwise fail! }
  if not LEnumerator.MoveNext() then
    ExceptionHelper.Throw_CollectionEmptyError();
  { Select the first element as comparison base }
  Result := LEnumerator.Current;
  { Iterate over the last N - 1 elements }
  while LEnumerator.MoveNext() do
  begin
    { Aggregate a value }
    Result := AAggregator(Result, LEnumerator.Current);
  end;
end;
function TEnexCollection<T>.AggregateOrDefault(const AAggregator: TFunc<T, T, T>; const ADefault: T): T;
var
  LEnumerator: IEnumerator<T>;
begin
  if not Assigned(AAggregator) then
    ExceptionHelper.Throw_ArgumentNilError('AAggregator');
  { Retrieve the enumerator object and type }
  LEnumerator := GetEnumerator();
  { Get the first object in the enumeration, otherwise fail! }
  if not LEnumerator.MoveNext() then
    Exit(ADefault);
  { Select the first element as comparison base }
  Result := LEnumerator.Current;
  { Iterate over the last N - 1 elements }
  while LEnumerator.MoveNext() do
  begin
    { Aggregate a value }
    Result := AAggregator(Result, LEnumerator.Current);
  end;
end;
function TEnexCollection<T>.All(const APredicate: TFunc<T, Boolean>): Boolean;
var
  LEnumerator: IEnumerator<T>;
begin
  if not Assigned(APredicate) then
    ExceptionHelper.Throw_ArgumentNilError('APredicate');
  { Retrieve the enumerator object }
  LEnumerator := GetEnumerator();
  { Iterate while ALL elements support the predicate }
  while LEnumerator.MoveNext() do
  begin
    if not APredicate(LEnumerator.Current) then
      Exit(false);
  end;
  Result := true;
end;
function TEnexCollection<T>.Any(const APredicate: TFunc<T, Boolean>): Boolean;
var
  LEnumerator: IEnumerator<T>;
begin
  if not Assigned(APredicate) then
    ExceptionHelper.Throw_ArgumentNilError('APredicate');
  { Retrieve the enumerator object }
  LEnumerator := GetEnumerator();
  { Iterate until ANY element supports the predicate }
  while LEnumerator.MoveNext() do
  begin
    if APredicate(LEnumerator.Current) then
      Exit(true);
  end;
  Result := false;
end;
function TEnexCollection<T>.CompareElements(const ALeft, ARight: T): NativeInt;
begin
  { Lazy init }
  if not Assigned(FElementRules.FComparer) then
    FElementRules.FComparer := TComparer<T>.Default;
  Result := FElementRules.FComparer.Compare(ALeft, ARight);
end;
function TEnexCollection<T>.CompareTo(AObject: TObject): Integer;
var
  LIterSelf, LIterTo: IEnumerator<T>;
  LMovSelf, LMovTo: Boolean;
begin
  { Check if we can continue }
  if (not Assigned(AObject)) or (not AObject.InheritsFrom(TEnexCollection<T>)) then
    Result := 1
  else begin
    { Assume equality }
    Result := 0;
    { Get enumerators }
    LIterSelf := GetEnumerator();
    LIterTo := TEnexCollection<T>(AObject).GetEnumerator();
    while true do
    begin
      { Iterate and verify that both enumerators moved }
      LMovSelf := LIterSelf.MoveNext();
      LMovTo := LIterTo.MoveNext();
      { If one moved but the other did not - error }
      if LMovSelf <> LMovTo then
      begin
        { Decide on the return value }
        if LMovSelf then
          Result := 1
        else
          Result := -1;
        Break;
      end;
      { If neither moved, we've reached the end }
      if not LMovSelf then
        Break;
      { Verify both values are identical }
      Result := CompareElements(LIterSelf.Current, LIterTo.Current);
      if Result <> 0 then
        Break;
    end;
  end;
end;
function TEnexCollection<T>.Concat(const ACollection: IEnexCollection<T>): IEnexCollection<T>;
begin
  { Check arguments }
  if not Assigned(ACollection) then
    ExceptionHelper.Throw_ArgumentNilError('ACollection');
  { Create concatenation iterator }
  Result := TEnexConcatCollection<T>.Create(Self, ACollection);
end;
constructor TEnexCollection<T>.Create(const ARules: TRules<T>);
begin
  FElementRules := ARules;
end;
constructor TEnexCollection<T>.Create;
begin
  Create(TRules<T>.Default);
end;
function TEnexCollection<T>.Distinct: IEnexCollection<T>;
begin
  { Create a new enumerator }
  Result := TEnexDistinctCollection<T>.Create(Self);
end;
function TEnexCollection<T>.ElementAt(const AIndex: NativeInt): T;
var
  LEnumerator: IEnumerator<T>;
  LCount: NativeInt;
begin
  { Retrieve the enumerator object }
  LEnumerator := GetEnumerator();
  LCount := 0;
  while LEnumerator.MoveNext() do
  begin
    { If we reached thge element, exit }
    if LCount = AIndex then
      Exit(LEnumerator.Current);
    Inc(LCount);
  end;
  { Fail! }
  ExceptionHelper.Throw_ArgumentOutOfRangeError('AIndex');
end;
function TEnexCollection<T>.ElementAtOrDefault(const AIndex: NativeInt; const ADefault: T): T;
var
  LEnumerator: IEnumerator<T>;
  LCount: NativeInt;
begin
  { Retrieve the enumerator object }
  LEnumerator := GetEnumerator();
  LCount := 0;
  while LEnumerator.MoveNext() do
  begin
    { If we reached thge element, exit }
    if LCount = AIndex then
      Exit(LEnumerator.Current);
    Inc(LCount);
  end;
  { Return default value }
  Result := ADefault;
end;
function TEnexCollection<T>.ElementsAreEqual(const ALeft, ARight: T): Boolean;
begin
  { Lazy init }
  if not Assigned(FElementRules.FEqComparer) then
    FElementRules.FEqComparer := TEqualityComparer<T>.Default;
  Result := FElementRules.FEqComparer.Equals(ALeft, ARight);
end;
function TEnexCollection<T>.Equals(Obj: TObject): Boolean;
begin
  { Call comparison }
  Result := (CompareTo(Obj) = 0);
end;
function TEnexCollection<T>.EqualsTo(const ACollection: IEnumerable<T>): Boolean;
var
  LEnumerator1, LEnumerator2: IEnumerator<T>;
  LMoved1, LMoved2: Boolean;
begin
  { Check arguments }
  if not Assigned(ACollection) then
    ExceptionHelper.Throw_ArgumentNilError('ACollection');
  { Get enumerators }
  LEnumerator1 := GetEnumerator();
  LEnumerator2 := ACollection.GetEnumerator();
  while true do
  begin
    { Iterate and verify that both enumerators moved }
    LMoved1 := LEnumerator1.MoveNext();
    LMoved2 := LEnumerator2.MoveNext();
    { If one moved but the other did not - error }
    if LMoved1 <> LMoved2 then
      Exit(false);
    { If neither moved, we've reached the end }
    if not LMoved1 then
      break;
    { Verify both values are identical }
    if not ElementsAreEqual(LEnumerator1.Current, LEnumerator2.Current) then
      Exit(false);
  end;
  { It worked! }
  Result := true;
end;
function TEnexCollection<T>.Exclude(const ACollection: IEnexCollection<T>): IEnexCollection<T>;
begin
  { Check arguments }
  if not Assigned(ACollection) then
    ExceptionHelper.Throw_ArgumentNilError('ACollection');
  { Create concatenation iterator }
  Result := TEnexExclusionCollection<T>.Create(Self, ACollection);
end;
class function TEnexCollection<T>.Fill(const AElement: T; const ACount: NativeInt; const ARules: TRules<T>): IEnexCollection<T>;
begin
  { Check arguments }
  if ACount <= 0 then
    ExceptionHelper.Throw_ArgumentOutOfRangeError('ACount');
  { Create an collection }
  Result := TEnexFillCollection<T>.Create(AElement, ACount, ARules);
end;
class function TEnexCollection<T>.Fill(const AElement: T; const ACount: NativeInt): IEnexCollection<T>;
begin
  { Call upper function }
  Result := Fill(AElement, ACount, TRules<T>.Default);
end;
function TEnexCollection<T>.First: T;
var
  LEnumerator: IEnumerator<T>;
begin
  { Retrieve the enumerator object }
  LEnumerator := GetEnumerator();
  { Get the first object in the enumeration, otherwise fail! }
  if LEnumerator.MoveNext() then
    Result := LEnumerator.Current
  else
    ExceptionHelper.Throw_CollectionEmptyError();
end;
function TEnexCollection<T>.FirstOrDefault(const ADefault: T): T;
var
  LEnumerator: IEnumerator<T>;
begin
  { Retrieve the enumerator object }
  LEnumerator := GetEnumerator();
  { Get the first object in the enumeration, otherwise return default! }
  if LEnumerator.MoveNext() then
    Result := LEnumerator.Current
  else
    Result := ADefault;
end;
function TEnexCollection<T>.FirstWhere(const APredicate: TFunc<T, Boolean>): T;
var
  LEnumerator: IEnumerator<T>;
  LWasOne: Boolean;
begin
  if not Assigned(APredicate) then
    ExceptionHelper.Throw_ArgumentNilError('APredicate');
  { Retrieve the enumerator object }
  LEnumerator := GetEnumerator();
  LWasOne := false;
  { Do the funky stuff already }
  while LEnumerator.MoveNext do
  begin
    LWasOne := true;
    if APredicate(LEnumerator.Current) then
      Exit(LEnumerator.Current);
  end;
  { Failure to find what we need }
  if LWasOne then
    ExceptionHelper.Throw_CollectionHasNoFilteredElements()
  else
    ExceptionHelper.Throw_CollectionEmptyError();
end;
function TEnexCollection<T>.FirstWhereBetween(const ALower, AHigher: T): T;
begin
  Result := FirstWhere(
    function(Arg1: T): Boolean
    begin
      Result := (CompareElements(Arg1, ALower) >= 0) and
                (CompareElements(Arg1, AHigher) <= 0)
    end
  );
end;
function TEnexCollection<T>.FirstWhereBetweenOrDefault(const ALower, AHigher, ADefault: T): T;
begin
  Result := FirstWhereOrDefault(
    function(Arg1: T): Boolean
    begin
      Result := (CompareElements(Arg1, ALower) >= 0) and
                (CompareElements(Arg1, AHigher) <= 0)
    end,
    ADefault
  );
end;
function TEnexCollection<T>.FirstWhereGreater(const ABound: T): T;
begin
  Result := FirstWhere(
    function(Arg1: T): Boolean
    begin
      Result := CompareElements(Arg1, ABound) > 0;
    end
  );
end;
function TEnexCollection<T>.FirstWhereGreaterOrDefault(const ABound, ADefault: T): T;
begin
  Result := FirstWhereOrDefault(
    function(Arg1: T): Boolean
    begin
      Result := CompareElements(Arg1, ABound) > 0;
    end,
    ADefault
  );
end;
function TEnexCollection<T>.FirstWhereGreaterOrEqual(const ABound: T): T;
begin
  Result := FirstWhere(
    function(Arg1: T): Boolean
    begin
      Result := CompareElements(Arg1, ABound) >= 0;
    end
  );
end;
function TEnexCollection<T>.FirstWhereGreaterOrEqualOrDefault(const ABound, ADefault: T): T;
begin
  Result := FirstWhereOrDefault(
    function(Arg1: T): Boolean
    begin
      Result := CompareElements(Arg1, ABound) >= 0;
    end,
    ADefault
  );
end;
function TEnexCollection<T>.FirstWhereLower(const ABound: T): T;
begin
  Result := FirstWhere(
    function(Arg1: T): Boolean
    begin
      Result := CompareElements(Arg1, ABound) < 0;
    end
  );
end;
function TEnexCollection<T>.FirstWhereLowerOrDefault(const ABound, ADefault: T): T;
begin
  Result := FirstWhereOrDefault(
    function(Arg1: T): Boolean
    begin
      Result := CompareElements(Arg1, ABound) < 0;
    end,
    ADefault
  );
end;
function TEnexCollection<T>.FirstWhereLowerOrEqual(const ABound: T): T;
begin
  Result := FirstWhere(
    function(Arg1: T): Boolean
    begin
      Result := CompareElements(Arg1, ABound) <= 0;
    end
  );
end;
function TEnexCollection<T>.FirstWhereLowerOrEqualOrDefault(const ABound, ADefault: T): T;
begin
  Result := FirstWhereOrDefault(
    function(Arg1: T): Boolean
    begin
      Result := CompareElements(Arg1, ABound) <= 0;
    end,
    ADefault
  );
end;
function TEnexCollection<T>.FirstWhereNot(const APredicate: TFunc<T, Boolean>): T;
begin
  if not Assigned(APredicate) then
    ExceptionHelper.Throw_ArgumentNilError('APredicate');
  Result := FirstWhere(
    function(Arg1: T): Boolean
    begin
      Result := not APredicate(Arg1);
    end
  );
end;
function TEnexCollection<T>.FirstWhereNotOrDefault(
  const APredicate: TFunc<T, Boolean>; const ADefault: T): T;
begin
  if not Assigned(APredicate) then
    ExceptionHelper.Throw_ArgumentNilError('APredicate');
  Result := FirstWhereOrDefault(
    function(Arg1: T): Boolean
    begin
      Result := not APredicate(Arg1);
    end,
    ADefault
  );
end;
function TEnexCollection<T>.FirstWhereOrDefault(const APredicate: TFunc<T, Boolean>; const ADefault: T): T;
var
  LEnumerator: IEnumerator<T>;
begin
  if not Assigned(APredicate) then
    ExceptionHelper.Throw_ArgumentNilError('APredicate');
  { Retrieve the enumerator object }
  LEnumerator := GetEnumerator();
  { Do the funky stuff already }
  while LEnumerator.MoveNext do
    if APredicate(LEnumerator.Current) then
      Exit(LEnumerator.Current);
  { Failure to find what we need }
  Result := ADefault;
end;
function TEnexCollection<T>.GetElementHashCode(const AValue: T): NativeInt;
begin
  { Lazy init }
  if not Assigned(FElementRules.FEqComparer) then
    FElementRules.FEqComparer := TEqualityComparer<T>.Default;
  Result := FElementRules.FEqComparer.GetHashCode(AValue);
end;
function TEnexCollection<T>.GetHashCode: Integer;
const
  CMagic = $0F;
var
  LEnumerator: IEnumerator<T>;
begin
  { Obtain the enumerator }
  LEnumerator := GetEnumerator();
  { Start at 0 }
  Result := 0;
  { ... }
  while LEnumerator.MoveNext() do
    Result := CMagic * Result + GetElementHashCode(LEnumerator.Current);
end;
procedure TEnexCollection<T>.HandleElementRemoved(const AElement: T);
begin
 // Nothing
end;
function TEnexCollection<T>.Intersect(const ACollection: IEnexCollection<T>): IEnexCollection<T>;
begin
  { Check arguments }
  if not Assigned(ACollection) then
    ExceptionHelper.Throw_ArgumentNilError('ACollection');
  { Create concatenation iterator }
  Result := TEnexIntersectionCollection<T>.Create(Self, ACollection);
end;
function TEnexCollection<T>.Last: T;
var
  LEnumerator: IEnumerator<T>;
begin
  { Retrieve the enumerator object }
  LEnumerator := GetEnumerator();
  { Get the first object in the enumeration, otherwise fail! }
  if not LEnumerator.MoveNext() then
    ExceptionHelper.Throw_CollectionEmptyError();
  { Iterate till the last element in the LEnumerator }
  while true do
  begin
    Result := LEnumerator.Current;
    { Exit if we hit the last element }
    if not LEnumerator.MoveNext() then
      Exit;
  end;
end;
function TEnexCollection<T>.LastOrDefault(const ADefault: T): T;
var
  LEnumerator: IEnumerator<T>;
begin
  { Retrieve the enumerator object }
  LEnumerator := GetEnumerator();
  { Get the first object in the enumeration, otherwise return default! }
  if not LEnumerator.MoveNext() then
    Exit(ADefault);
  { Iterate till the last element in the LEnumerator }
  while true do
  begin
    Result := LEnumerator.Current;
    { Exit if we hit the last element }
    if not LEnumerator.MoveNext() then
      Exit;
  end;
end;
function TEnexCollection<T>.Max: T;
var
  LEnumerator: IEnumerator<T>;
begin
  { Retrieve the enumerator object and type }
  LEnumerator := GetEnumerator();
  { Get the first object in the enumeration, otherwise fail! }
  if not LEnumerator.MoveNext() then
    ExceptionHelper.Throw_CollectionEmptyError();
  { Select the first element as comparison base }
  Result := LEnumerator.Current;
  { Iterate till the last element in the LEnumerator }
  while true do
  begin
    if CompareElements(LEnumerator.Current, Result) > 0 then
      Result := LEnumerator.Current;
    { Exit if we hit the last element }
    if not LEnumerator.MoveNext() then
      Exit;
  end;
end;
function TEnexCollection<T>.Min: T;
var
  LEnumerator: IEnumerator<T>;
begin
  { Retrieve the enumerator object and type }
  LEnumerator := GetEnumerator();
  { Get the first object in the enumeration, otherwise fail! }
  if not LEnumerator.MoveNext() then
    ExceptionHelper.Throw_CollectionEmptyError();
  { Select the first element as comparison base }
  Result := LEnumerator.Current;
  { Iterate till the last element in the LEnumerator }
  while true do
  begin
    if CompareElements(LEnumerator.Current, Result) < 0 then
      Result := LEnumerator.Current;
    { Exit if we hit the last element }
    if not LEnumerator.MoveNext() then
      Exit;
  end;
end;
procedure TEnexCollection<T>.NotifyElementRemoved(const AElement: T);
begin
  { Handle removal }
  if Assigned(FRemoveNotification) then
    FRemoveNotification(AElement)
  else
    HandleElementRemoved(AElement);
end;
function TEnexCollection<T>.Op: TEnexExtOps<T>;
begin
  { Build up the record + keep an optional reference to the object }
  Result.FInstance := Self;
  Result.FKeepAlive := Self.ExtractReference;
  Result.FRules := FElementRules;
end;
function TEnexCollection<T>.Range(const AStart, AEnd: NativeInt): IEnexCollection<T>;
begin
  { Create a new Enex collection }
  Result := TEnexRangeCollection<T>.Create(Self, AStart, AEnd);
end;
function TEnexCollection<T>.Reversed: IEnexCollection<T>;
var
  LList: TList<T>;
begin
  { Create an itermediary LList }
  LList := TList<T>.Create(Self);
  LList.Reverse();
  { Pass the LList further }
  Result := LList;
end;
function TEnexCollection<T>.Skip(const ACount: NativeInt): IEnexCollection<T>;
begin
  { Check parameters }
  if ACount = 0 then
    ExceptionHelper.Throw_ArgumentOutOfRangeError('ACount');
  { Create a new Enex collection }
  Result := TEnexSkipCollection<T>.Create(Self, ACount);
end;
function TEnexCollection<T>.SkipWhile(const APredicate: TFunc<T, Boolean>): IEnexCollection<T>;
begin
  { Check arguments }
  if not Assigned(APredicate) then
    ExceptionHelper.Throw_ArgumentNilError('APredicate');
  { Create a new Enex collection }
  Result := TEnexSkipWhileCollection<T>.Create(Self, APredicate);
end;
function TEnexCollection<T>.SkipWhileBetween(const ALower, AHigher: T): IEnexCollection<T>;
var
  LLower, LHigher: T;
begin
  { Locals }
  LLower := ALower;
  LHigher := AHigher;
  { Use SkipWhile() and pass an anonymous function }
  Result := SkipWhile(
    function(Arg1: T): Boolean
    begin
      Exit((CompareElements(Arg1, LLower) >= 0) and (CompareElements(Arg1, LHigher) <= 0));
    end
  );
end;
function TEnexCollection<T>.SkipWhileGreater(const ABound: T): IEnexCollection<T>;
var
  LBound: T;
begin
  { Locals }
  LBound := ABound;
  { Use SkipWhile() and pass an anonymous function }
  Result := SkipWhile(
    function(Arg1: T): Boolean
    begin
      Exit(CompareElements(Arg1, LBound) > 0);
    end
  );
end;
function TEnexCollection<T>.SkipWhileGreaterOrEqual(const ABound: T): IEnexCollection<T>;
var
  LBound: T;
begin
  { Locals }
  LBound := ABound;
  { Use SkipWhile() and pass an anonymous function }
  Result := SkipWhile(
    function(Arg1: T): Boolean
    begin
      Exit(CompareElements(Arg1, LBound) >= 0);
    end
  );
end;
function TEnexCollection<T>.SkipWhileLower(const ABound: T): IEnexCollection<T>;
var
  LBound: T;
begin
  { Locals }
  LBound := ABound;
  { Use SkipWhile() and pass an anonymous function }
  Result := SkipWhile(
    function(Arg1: T): Boolean
    begin
      Exit(CompareElements(Arg1, LBound) < 0);
    end
  );
end;
function TEnexCollection<T>.SkipWhileLowerOrEqual(const ABound: T): IEnexCollection<T>;
var
  LBound: T;
begin
  { Locals }
  LBound := ABound;
  { Use SkipWhile() and pass an anonymous function }
  Result := SkipWhile(
    function(Arg1: T): Boolean
    begin
      Exit(CompareElements(Arg1, LBound) <= 0);
    end
  );
end;
function TEnexCollection<T>.Ordered(const ASortProc: TComparison<T>): IEnexCollection<T>;
var
  LList: TList<T>;
begin
  { Create an itermediary LList }
  LList := TList<T>.Create(Self);
  LList.Sort(ASortProc);
  { Pass the LList further }
  Result := LList;
end;
function TEnexCollection<T>.Ordered(const AAscending: Boolean = true): IEnexCollection<T>;
var
  LList: TList<T>;
begin
  { Create an itermediary LList }
  LList := TList<T>.Create(Self);
  LList.Sort(AAscending);
  { Pass the LList further }
  Result := LList;
end;
function TEnexCollection<T>.Take(const ACount: NativeInt): IEnexCollection<T>;
begin
  { Check parameters }
  if ACount = 0 then
    ExceptionHelper.Throw_ArgumentOutOfRangeError('ACount');
  { Create a new Enex collection }
  Result := TEnexTakeCollection<T>.Create(Self, ACount);
end;
function TEnexCollection<T>.TakeWhile(const APredicate: TFunc<T, Boolean>): IEnexCollection<T>;
begin
  { Check arguments }
  if not Assigned(APredicate) then
    ExceptionHelper.Throw_ArgumentNilError('APredicate');
  { Create a new Enex collection }
  Result := TEnexTakeWhileCollection<T>.Create(Self, APredicate);
end;
function TEnexCollection<T>.TakeWhileBetween(const ALower, AHigher: T): IEnexCollection<T>;
var
  LLower, LHigher: T;
begin
  { Locals }
  LLower := ALower;
  LHigher := AHigher;
  { Use TakeWhile() and pass an anonymous function }
  Result := TakeWhile(
    function(Arg1: T): Boolean
    begin
      Exit((CompareElements(Arg1, LLower) >= 0) and (CompareElements(Arg1, LHigher) <= 0));
    end
  );
end;
function TEnexCollection<T>.TakeWhileGreater(const ABound: T): IEnexCollection<T>;
var
  LBound: T;
begin
  { Locals }
  LBound := ABound;
  { Use TakeWhile() and pass an anonymous function }
  Result := TakeWhile(
    function(Arg1: T): Boolean
    begin
      Exit(CompareElements(Arg1, LBound) > 0);
    end
  );
end;
function TEnexCollection<T>.TakeWhileGreaterOrEqual(const ABound: T): IEnexCollection<T>;
var
  LBound: T;
begin
  { Locals }
  LBound := ABound;
  { Use TakeWhile() and pass an anonymous function }
  Result := TakeWhile(
    function(Arg1: T): Boolean
    begin
      Exit(CompareElements(Arg1, LBound) >= 0);
    end
  );
end;
function TEnexCollection<T>.TakeWhileLower(const ABound: T): IEnexCollection<T>;
var
  LBound: T;
begin
  { Locals }
  LBound := ABound;
  { Use TakeWhile() and pass an anonymous function }
  Result := TakeWhile(
    function(Arg1: T): Boolean
    begin
      Exit(CompareElements(Arg1, LBound) < 0);
    end
  );
end;
function TEnexCollection<T>.TakeWhileLowerOrEqual(const ABound: T): IEnexCollection<T>;
var
  LBound: T;
begin
  { Locals }
  LBound := ABound;
  { Use TakeWhile() and pass an anonymous function }
  Result := TakeWhile(
    function(Arg1: T): Boolean
    begin
      Exit(CompareElements(Arg1, LBound) <= 0);
    end
  );
end;
function TEnexCollection<T>.ToList: IList<T>;
begin
  { Simply make up a list }
  Result := TList<T>.Create(Self);
end;
function TEnexCollection<T>.ToSet: ISet<T>;
begin
  { Simply make up a bag }
  Result := THashSet<T>.Create(Self);
end;
function TEnexCollection<T>.Union(const ACollection: IEnexCollection<T>): IEnexCollection<T>;
begin
  { Check arguments }
  if not Assigned(ACollection) then
    ExceptionHelper.Throw_ArgumentNilError('ACollection');
  { Create concatenation iterator }
  Result := TEnexUnionCollection<T>.Create(Self, ACollection);
end;
function TEnexCollection<T>.Where(const APredicate: TFunc<T, Boolean>): IEnexCollection<T>;
begin
  { Check arguments }
  if not Assigned(APredicate) then
    ExceptionHelper.Throw_ArgumentNilError('APredicate');
  { Create a new Enex collection }
  Result := TEnexWhereCollection<T>.Create(Self, APredicate, False); // Don't invert the result
end;
function TEnexCollection<T>.WhereBetween(const ALower, AHigher: T): IEnexCollection<T>;
var
  LLower, LHigher: T;
begin
  { Locals }
  LLower := ALower;
  LHigher := AHigher;
  { Use Where() and pass an anonymous function }
  Result := Where(
    function(Arg1: T): Boolean
    begin
      Exit((CompareElements(Arg1, LLower) >= 0) and (CompareElements(Arg1, LHigher) <= 0));
    end
  );
end;
function TEnexCollection<T>.WhereGreater(const ABound: T): IEnexCollection<T>;
var
  LBound: T;
begin
  { Locals }
  LBound := ABound;
  { Use Where() and pass an anonymous function }
  Result := Where(
    function(Arg1: T): Boolean
    begin
      Exit(CompareElements(Arg1, LBound) > 0);
    end
  );
end;
function TEnexCollection<T>.WhereGreaterOrEqual(const ABound: T): IEnexCollection<T>;
var
  LBound: T;
begin
  { Locals }
  LBound := ABound;
  { Use Where() and pass an anonymous function }
  Result := Where(
    function(Arg1: T): Boolean
    begin
      Exit(CompareElements(Arg1, LBound) >= 0);
    end
  );
end;
function TEnexCollection<T>.WhereLower(const ABound: T): IEnexCollection<T>;
var
  LBound: T;
begin
  { Locals }
  LBound := ABound;
  { Use Where() and pass an anonymous function }
  Result := Where(
    function(Arg1: T): Boolean
    begin
      Exit(CompareElements(Arg1, LBound) < 0);
    end
  );
end;
function TEnexCollection<T>.WhereLowerOrEqual(const ABound: T): IEnexCollection<T>;
var
  LBound: T;
begin
  { Locals }
  LBound := ABound;
  { Use Where() and pass an anonymous function }
  Result := Where(
    function(Arg1: T): Boolean
    begin
      Exit(CompareElements(Arg1, LBound) <= 0);
    end
  );
end;
function TEnexCollection<T>.WhereNot(
  const APredicate: TFunc<T, Boolean>): IEnexCollection<T>;
begin
  { Check arguments }
  if not Assigned(APredicate) then
    ExceptionHelper.Throw_ArgumentNilError('APredicate');
  { Create a new Enex collection }
  Result := TEnexWhereCollection<T>.Create(Self, APredicate, True); // Invert the result
end;
{ TEnexAssociativeCollection<TKey, TValue> }
constructor TEnexAssociativeCollection<TKey, TValue>.Create;
begin
  Create(TRules<TKey>.Default, TRules<TValue>.Default);
end;
function TEnexAssociativeCollection<TKey, TValue>.CompareKeys(const ALeft, ARight: TKey): NativeInt;
begin
  { Lazy init }
  if not Assigned(FKeyRules.FComparer) then
    FKeyRules.FComparer := TComparer<TKey>.Default;
  Result := FKeyRules.FComparer.Compare(ALeft, ARight);
end;
function TEnexAssociativeCollection<TKey, TValue>.CompareValues(const ALeft, ARight: TValue): NativeInt;
begin
  { Lazy init }
  if not Assigned(FValueRules.FComparer) then
    FValueRules.FComparer := TComparer<TValue>.Default;
  Result := FValueRules.FComparer.Compare(ALeft, ARight);
end;
constructor TEnexAssociativeCollection<TKey, TValue>.Create(const AKeyRules: TRules<TKey>; const AValueRules: TRules<TValue>);
begin
  FKeyRules := AKeyRules;
  FValueRules := AValueRules;
end;
function TEnexAssociativeCollection<TKey, TValue>.DistinctByKeys: IEnexAssociativeCollection<TKey, TValue>;
begin
  Result := TEnexAssociativeDistinctByKeysCollection<TKey, TValue>.Create(Self);
end;
function TEnexAssociativeCollection<TKey, TValue>.DistinctByValues: IEnexAssociativeCollection<TKey, TValue>;
begin
  Result := TEnexAssociativeDistinctByValuesCollection<TKey, TValue>.Create(Self);
end;
function TEnexAssociativeCollection<TKey, TValue>.GetKeyHashCode(const AValue: TKey): NativeInt;
begin
  { Lazy init }
  if not Assigned(FKeyRules.FEqComparer) then
    FKeyRules.FEqComparer := TEqualityComparer<TKey>.Default;
  Result := FKeyRules.FEqComparer.GetHashCode(AValue);
end;
function TEnexAssociativeCollection<TKey, TValue>.GetValueHashCode(const AValue: TValue): NativeInt;
begin
  { Lazy init }
  if not Assigned(FValueRules.FEqComparer) then
    FValueRules.FEqComparer := TEqualityComparer<TValue>.Default;
  Result := FValueRules.FEqComparer.GetHashCode(AValue);
end;
procedure TEnexAssociativeCollection<TKey, TValue>.HandleKeyRemoved(const AKey: TKey);
begin
  // Nothing!
end;
procedure TEnexAssociativeCollection<TKey, TValue>.HandleValueRemoved(const AValue: TValue);
begin
  // Nothing!
end;
function TEnexAssociativeCollection<TKey, TValue>.Includes(const ACollection: IEnumerable<TPair<TKey, TValue>>): Boolean;
var
  LEnumerator: IEnumerator<TPair<TKey, TValue>>;
begin
  { Retrieve the enumerator object }
  LEnumerator := ACollection.GetEnumerator();
  { Iterate till the last element in the LEnumerator }
  while LEnumerator.MoveNext do
  begin
    if not KeyHasValue(LEnumerator.Current.Key, LEnumerator.Current.Value) then
      Exit(false);
  end;
  { We got here, it means all is OK }
  Result := true;
end;
function TEnexAssociativeCollection<TKey, TValue>.KeyHasValue(const AKey: TKey; const AValue: TValue): Boolean;
var
  LEnumerator: IEnumerator<TPair<TKey, TValue>>;
begin
  { Retrieve the enumerator object and type }
  LEnumerator := GetEnumerator();
  { Iterate till the last element in the LEnumerator }
  while LEnumerator.MoveNext do
  begin
    if KeysAreEqual(LEnumerator.Current.Key, AKey) and
       ValuesAreEqual(LEnumerator.Current.Value, AValue) then
      Exit(true);
  end;
  { No found! }
  Result := false;
end;
function TEnexAssociativeCollection<TKey, TValue>.KeysAreEqual(const ALeft, ARight: TKey): Boolean;
begin
  { Lazy init }
  if not Assigned(FKeyRules.FEqComparer) then
    FKeyRules.FEqComparer := TEqualityComparer<TKey>.Default;
  Result := FKeyRules.FEqComparer.Equals(ALeft, ARight);
end;
function TEnexAssociativeCollection<TKey, TValue>.MaxKey: TKey;
var
  LEnumerator: IEnumerator<TPair<TKey, TValue>>;
begin
  { Retrieve the enumerator object and type }
  LEnumerator := GetEnumerator();
  { Get the first object in the enumeration, otherwise fail! }
  if not LEnumerator.MoveNext() then
    ExceptionHelper.Throw_CollectionEmptyError();
  { Select the first element as comparison base }
  Result := LEnumerator.Current.Key;
  { Iterate till the last element in the LEnumerator }
  while true do
  begin
    if CompareKeys(LEnumerator.Current.Key, Result) > 0 then
      Result := LEnumerator.Current.Key;
    { Exit if we hit the last element }
    if not LEnumerator.MoveNext() then
      Exit;
  end;
end;
function TEnexAssociativeCollection<TKey, TValue>.MaxValue: TValue;
var
  LEnumerator: IEnumerator<TPair<TKey, TValue>>;
begin
  { Retrieve the enumerator object and type }
  LEnumerator := GetEnumerator();
  { Get the first object in the enumeration, otherwise fail! }
  if not LEnumerator.MoveNext() then
    ExceptionHelper.Throw_CollectionEmptyError();
  { Select the first element as comparison base }
  Result := LEnumerator.Current.Value;
  { Iterate till the last element in the LEnumerator }
  while true do
  begin
    if CompareValues(LEnumerator.Current.Value, Result) > 0 then
      Result := LEnumerator.Current.Value;
    { Exit if we hit the last element }
    if not LEnumerator.MoveNext() then
      Exit;
  end;
end;
function TEnexAssociativeCollection<TKey, TValue>.MinKey: TKey;
var
  LEnumerator: IEnumerator<TPair<TKey, TValue>>;
begin
  { Retrieve the enumerator object and type }
  LEnumerator := GetEnumerator();
  { Get the first object in the enumeration, otherwise fail! }
  if not LEnumerator.MoveNext() then
    ExceptionHelper.Throw_CollectionEmptyError();
  { Select the first element as comparison base }
  Result := LEnumerator.Current.Key;
  { Iterate till the last element in the LEnumerator }
  while true do
  begin
    if CompareKeys(LEnumerator.Current.Key, Result) < 0 then
      Result := LEnumerator.Current.Key;
    { Exit if we hit the last element }
    if not LEnumerator.MoveNext() then
      Exit;
  end;
end;
function TEnexAssociativeCollection<TKey, TValue>.MinValue: TValue;
var
  LEnumerator: IEnumerator<TPair<TKey, TValue>>;
begin
  { Retrieve the enumerator object and type }
  LEnumerator := GetEnumerator();
  { Get the first object in the enumeration, otherwise fail! }
  if not LEnumerator.MoveNext() then
    ExceptionHelper.Throw_CollectionEmptyError();
  { Select the first element as comparison base }
  Result := LEnumerator.Current.Value;
  { Iterate till the last element in the LEnumerator }
  while true do
  begin
    if CompareValues(LEnumerator.Current.Value, Result) < 0 then
      Result := LEnumerator.Current.Value;
    { Exit if we hit the last element }
    if not LEnumerator.MoveNext() then
      Exit;
  end;
end;
procedure TEnexAssociativeCollection<TKey, TValue>.NotifyKeyRemoved(const AKey: TKey);
begin
  { Handle stuff }
  if Assigned(FKeyRemoveNotification) then
    FKeyRemoveNotification(AKey)
  else
    HandleKeyRemoved(AKey);
end;
procedure TEnexAssociativeCollection<TKey, TValue>.NotifyValueRemoved(const AValue: TValue);
begin
  { Handle stuff }
  if Assigned(FValueRemoveNotification) then
    FValueRemoveNotification(AValue)
  else
    HandleValueRemoved(AValue);
end;
function TEnexAssociativeCollection<TKey, TValue>.SelectKeys: IEnexCollection<TKey>;
begin
  { Create a selector }
  Result := TEnexSelectKeysCollection<TKey, TValue>.Create(Self);
end;
function TEnexAssociativeCollection<TKey, TValue>.SelectValues: IEnexCollection<TValue>;
begin
  { Create a selector }
  Result := TEnexSelectValuesCollection<TKey, TValue>.Create(Self);
end;
function TEnexAssociativeCollection<TKey, TValue>.ToDictionary: IDictionary<TKey, TValue>;
begin
  Result := TDictionary<TKey, TValue>.Create(Self);
end;
function TEnexAssociativeCollection<TKey, TValue>.ValueForKey(const AKey: TKey): TValue;
var
  LEnumerator: IEnumerator<TPair<TKey, TValue>>;
begin
  { Retrieve the enumerator object and type }
  LEnumerator := GetEnumerator();
  { Iterate till the last element in the LEnumerator }
  while LEnumerator.MoveNext do
  begin
    if KeysAreEqual(LEnumerator.Current.Key, AKey) then
      Exit(LEnumerator.Current.Value);
  end;
  { If nothing found, simply raise an exception }
  ExceptionHelper.Throw_KeyNotFoundError('AKey');
end;
function TEnexAssociativeCollection<TKey, TValue>.ValuesAreEqual(const ALeft, ARight: TValue): Boolean;
begin
  { Lazy init }
  if not Assigned(FValueRules.FEqComparer) then
    FValueRules.FEqComparer := TEqualityComparer<TValue>.Default;
  Result := FValueRules.FEqComparer.Equals(ALeft, ARight);
end;
function TEnexAssociativeCollection<TKey, TValue>.Where(
  const APredicate: TFunc<TKey, TValue, Boolean>): IEnexAssociativeCollection<TKey, TValue>;
begin
  { Check arguments }
  if not Assigned(APredicate) then
    ExceptionHelper.Throw_ArgumentNilError('APredicate');
  { Create a new Enex collection }
  Result := TEnexAssociativeWhereCollection<TKey, TValue>.Create(Self, APredicate, False); // Don't invert the result
end;
function TEnexAssociativeCollection<TKey, TValue>.WhereKeyBetween(const ALower,
  AHigher: TKey): IEnexAssociativeCollection<TKey, TValue>;
var
  LLower, LHigher: TKey;
begin
  { Locals }
  LLower := ALower;
  LHigher := AHigher;
  { Use Where() and pass an anonymous function }
  Result := Where(
    function(Arg1: TKey; Arg2: TValue): Boolean
    begin
      Exit((CompareKeys(Arg1, LLower) >= 0) and (CompareKeys(Arg1, LHigher) <= 0));
    end
  );
end;
function TEnexAssociativeCollection<TKey, TValue>.WhereKeyGreater(
  const ABound: TKey): IEnexAssociativeCollection<TKey, TValue>;
var
  LBound: TKey;
begin
  { Locals }
  LBound := ABound;
  { Use Where() and pass an anonymous function }
  Result := Where(
    function(Arg1: TKey; Arg2: TValue): Boolean
    begin
      Exit(CompareKeys(Arg1, LBound) > 0);
    end
  );
end;
function TEnexAssociativeCollection<TKey, TValue>.WhereKeyGreaterOrEqual(
  const ABound: TKey): IEnexAssociativeCollection<TKey, TValue>;
var
  LBound: TKey;
begin
  { Locals }
  LBound := ABound;
  { Use Where() and pass an anonymous function }
  Result := Where(
    function(Arg1: TKey; Arg2: TValue): Boolean
    begin
      Exit(CompareKeys(Arg1, LBound) >= 0);
    end
  );
end;
function TEnexAssociativeCollection<TKey, TValue>.WhereKeyLower(
  const ABound: TKey): IEnexAssociativeCollection<TKey, TValue>;
var
  LBound: TKey;
  LRules: TRules<TKey>;
begin
  { Locals }
  LBound := ABound;
  { Use Where() and pass an anonymous function }
  Result := Where(
    function(Arg1: TKey; Arg2: TValue): Boolean
    begin
      Exit(CompareKeys(Arg1, LBound) < 0);
    end
  );
end;
function TEnexAssociativeCollection<TKey, TValue>.WhereKeyLowerOrEqual(
  const ABound: TKey): IEnexAssociativeCollection<TKey, TValue>;
var
  LBound: TKey;
begin
  { Locals }
  LBound := ABound;
  { Use Where() and pass an anonymous function }
  Result := Where(
    function(Arg1: TKey; Arg2: TValue): Boolean
    begin
      Exit(CompareKeys(Arg1, LBound) <= 0);
    end
  );
end;
function TEnexAssociativeCollection<TKey, TValue>.WhereNot(
  const APredicate: TFunc<TKey, TValue, Boolean>): IEnexAssociativeCollection<TKey, TValue>;
begin
  { Check arguments }
  if not Assigned(APredicate) then
    ExceptionHelper.Throw_ArgumentNilError('APredicate');
  { Create a new Enex collection }
  Result := TEnexAssociativeWhereCollection<TKey, TValue>.Create(Self, APredicate, True); // Invert the result
end;
function TEnexAssociativeCollection<TKey, TValue>.WhereValueBetween(
  const ALower, AHigher: TValue): IEnexAssociativeCollection<TKey, TValue>;
var
  LLower, LHigher: TValue;
begin
  { Locals }
  LLower := ALower;
  LHigher := AHigher;
  { Use Where() and pass an anonymous function }
  Result := Where(
    function(Arg1: TKey; Arg2: TValue): Boolean
    begin
      Exit((CompareValues(Arg2, LLower) >= 0) and (CompareValues(Arg2, LHigher) <= 0));
    end
  );
end;
function TEnexAssociativeCollection<TKey, TValue>.WhereValueGreater(
  const ABound: TValue): IEnexAssociativeCollection<TKey, TValue>;
var
  LBound: TValue;
begin
  { Locals }
  LBound := ABound;
  { Use Where() and pass an anonymous function }
  Result := Where(
    function(Arg1: TKey; Arg2: TValue): Boolean
    begin
      Exit(CompareValues(Arg2, LBound) > 0);
    end
  );
end;
function TEnexAssociativeCollection<TKey, TValue>.WhereValueGreaterOrEqual(
  const ABound: TValue): IEnexAssociativeCollection<TKey, TValue>;
var
  LBound: TValue;
begin
  { Locals }
  LBound := ABound;
  { Use Where() and pass an anonymous function }
  Result := Where(
    function(Arg1: TKey; Arg2: TValue): Boolean
    begin
      Exit(CompareValues(Arg2, LBound) >= 0);
    end
  );
end;
function TEnexAssociativeCollection<TKey, TValue>.WhereValueLower(
  const ABound: TValue): IEnexAssociativeCollection<TKey, TValue>;
var
  LBound: TValue;
begin
  { Locals }
  LBound := ABound;
  { Use Where() and pass an anonymous function }
  Result := Where(
    function(Arg1: TKey; Arg2: TValue): Boolean
    begin
      Exit(CompareValues(Arg2, LBound) < 0);
    end
  );
end;
function TEnexAssociativeCollection<TKey, TValue>.WhereValueLowerOrEqual(
  const ABound: TValue): IEnexAssociativeCollection<TKey, TValue>;
var
  LBound: TValue;
begin
  { Locals }
  LBound := ABound;
  { Use Where() and pass an anonymous function }
  Result := Where(
    function(Arg1: TKey; Arg2: TValue): Boolean
    begin
      Exit(CompareValues(Arg2, LBound) <= 0);
    end
  );
end;
{ TEnexWhereCollection<T> }
constructor TEnexWhereCollection<T>.Create(const ACollection: TEnexCollection<T>;
  const APredicate: TFunc<T, Boolean>; const AInvertResult: Boolean);
begin
  { Check arguments }
  if not Assigned(APredicate) then
    ExceptionHelper.Throw_ArgumentNilError('APredicate');
  if not Assigned(ACollection) then
    ExceptionHelper.Throw_ArgumentNilError('ACollection');
  inherited Create(ACollection.ElementRules);
  { Assign internals }
  FCollection := ACollection;
  KeepObjectAlive(FCollection);
  FPredicate := APredicate;
  FInvertResult := AInvertResult;
end;
destructor TEnexWhereCollection<T>.Destroy;
begin
  { Delete the enumerable if required }
  ReleaseObject(FCollection, false);
  inherited;
end;
function TEnexWhereCollection<T>.GetEnumerator: IEnumerator<T>;
begin
  { Generate an enumerator }
  Result := TEnumerator.Create(Self);
end;
{ TEnexWhereCollection<T>.TEnumerator }
constructor TEnexWhereCollection<T>.TEnumerator.Create(const ACollection: TEnexWhereCollection<T>);
begin
  { Initialize }
  FCollection := ACollection;
  KeepObjectAlive(FCollection);
  FEnumerator:= ACollection.FCollection.GetEnumerator();
end;
destructor TEnexWhereCollection<T>.TEnumerator.Destroy;
begin
  ReleaseObject(FCollection);
  inherited;
end;
function TEnexWhereCollection<T>.TEnumerator.GetCurrent: T;
begin
  { Get current element of the "sub-enumerable" object }
  Result := FEnumerator.Current;
end;
function TEnexWhereCollection<T>.TEnumerator.MoveNext: Boolean;
begin
  { Iterate until given condition is met on an element }
  while True do
  begin
    Result := FEnumerator.MoveNext;
    { Terminate on sub-enum termination }
    if not Result then
      Exit;
    { Check whether the current element meets the condition and exit }
    { ... otherwise continue to the next iteration }
    if FCollection.FPredicate(FEnumerator.Current) xor FCollection.FInvertResult then
      Exit;
  end;
end;
{ TEnexSelectCollection<T, TOut> }
constructor TEnexSelectCollection<T, TOut>.Create(const ACollection: TEnexCollection<T>;
  const ASelector: TFunc<T, TOut>; const ARules: TRules<TOut>);
begin
  { Check arguments }
  if not Assigned(ASelector) then
    ExceptionHelper.Throw_ArgumentNilError('ASelector');
  if not Assigned(ACollection) then
    ExceptionHelper.Throw_ArgumentNilError('ACollection');
  { Rules ... }
  inherited Create(ARules);
  { Assign internals }
  FCollection := ACollection;
  KeepObjectAlive(FCollection);
  FSelector := ASelector;
end;
destructor TEnexSelectCollection<T, TOut>.Destroy;
begin
  { Delete the enumerable if required }
  ReleaseObject(FCollection, false);
  inherited;
end;
function TEnexSelectCollection<T, TOut>.ElementAt(const AIndex: NativeInt): TOut;
begin
  Result := FSelector(FCollection.ElementAt(AIndex));
end;
function TEnexSelectCollection<T, TOut>.Empty: Boolean;
begin
  Result := FCollection.Empty;
end;
function TEnexSelectCollection<T, TOut>.First: TOut;
begin
  Result := FSelector(FCollection.First);
end;
function TEnexSelectCollection<T, TOut>.GetCount: NativeInt;
begin
  Result := FCollection.GetCount();
end;
function TEnexSelectCollection<T, TOut>.GetEnumerator: IEnumerator<TOut>;
begin
  { Generate an enumerator }
  Result := TEnumerator.Create(Self);
end;
function TEnexSelectCollection<T, TOut>.Last: TOut;
begin
  Result := FSelector(FCollection.Last);
end;
function TEnexSelectCollection<T, TOut>.Single: TOut;
begin
  Result := FSelector(FCollection.Single);
end;
{ TEnexSelectCollection<T, TOut>.TEnumerator }
constructor TEnexSelectCollection<T, TOut>.TEnumerator.Create(const ACollection: TEnexSelectCollection<T, TOut>);
begin
  { Initialize }
  FCollection := ACollection;
  KeepObjectAlive(FCollection);
  FEnumerator := ACollection.FCollection.GetEnumerator();
  FCurrent := default(TOut);
end;
destructor TEnexSelectCollection<T, TOut>.TEnumerator.Destroy;
begin
  ReleaseObject(FCollection);
  inherited;
end;
function TEnexSelectCollection<T, TOut>.TEnumerator.GetCurrent: TOut;
begin
  { Get current element of the "sub-enumerable" object }
  Result := FCurrent;
end;
function TEnexSelectCollection<T, TOut>.TEnumerator.MoveNext: Boolean;
begin
  { Next iteration }
  Result := FEnumerator.MoveNext;
  { Terminate on sub-enum termination }
  if not Result then
    Exit;
  { Return the next "selected" element }
  FCurrent := FCollection.FSelector(FEnumerator.Current);
end;
{ TEnexConcatCollection<T> }
function TEnexConcatCollection<T>.All(const APredicate: TFunc<T, Boolean>): Boolean;
begin
  Result := FCollection1.All(APredicate) and FCollection2.All(APredicate);
end;
function TEnexConcatCollection<T>.Any(const APredicate: TFunc<T, Boolean>): Boolean;
begin
  Result := FCollection1.Any(APredicate) or FCollection2.Any(APredicate);
end;
constructor TEnexConcatCollection<T>.Create(
  const ACollection1: TEnexCollection<T>; const ACollection2: IEnexCollection<T>);
begin
  { Check arguments }
  if not Assigned(ACollection1) then
    ExceptionHelper.Throw_ArgumentNilError('ACollection1');
  if not Assigned(ACollection2) then
    ExceptionHelper.Throw_ArgumentNilError('ACollection2');
  { Rules ... }
  inherited Create(ACollection1.ElementRules);
  { Assign internals }
  FCollection1 := ACollection1;
  KeepObjectAlive(FCollection1);
  FCollection2 := ACollection2;
end;
destructor TEnexConcatCollection<T>.Destroy;
begin
  { Delete the enumerable if required }
  ReleaseObject(FCollection1, false);
  inherited;
end;
function TEnexConcatCollection<T>.Empty: Boolean;
begin
  Result := (GetCount = 0);
end;
function TEnexConcatCollection<T>.GetCount: NativeInt;
begin
  Result := FCollection1.GetCount() + FCollection2.GetCount();
end;
function TEnexConcatCollection<T>.GetEnumerator: IEnumerator<T>;
begin
  { Create enumerator }
  Result := TEnumerator.Create(Self);
end;
{ TEnexConcatCollection<T>.TEnumerator }
constructor TEnexConcatCollection<T>.TEnumerator.Create(const ACollection: TEnexConcatCollection<T>);
begin
  { Initialize }
  FCollection := ACollection;
  KeepObjectAlive(FCollection);
  FEnumerator1 := ACollection.FCollection1.GetEnumerator();
  FEnumerator2 := ACollection.FCollection2.GetEnumerator();
end;
destructor TEnexConcatCollection<T>.TEnumerator.Destroy;
begin
  ReleaseObject(FCollection);
  inherited;
end;
function TEnexConcatCollection<T>.TEnumerator.GetCurrent: T;
begin
  { Pass the first and then the last }
  if Assigned(FEnumerator1) then
    Result := FEnumerator1.Current
  else
    Result := FEnumerator2.Current;
end;
function TEnexConcatCollection<T>.TEnumerator.MoveNext: Boolean;
begin
  if Assigned(FEnumerator1) then
  begin
    { Iterate over 1 }
    Result := FEnumerator1.MoveNext();
    { Succesefully iterated collection 1 }
    if Result then
      Exit;
    { We've reached the bottom of 1 }
    FEnumerator1 := nil;
  end;
  { Iterate over 2 now }
  Result := FEnumerator2.MoveNext();
end;
{ TEnexUnionCollection<T> }
constructor TEnexUnionCollection<T>.Create(
  const ACollection1: TEnexCollection<T>; const ACollection2: IEnexCollection<T>);
begin
  { Check arguments }
  if not Assigned(ACollection1) then
    ExceptionHelper.Throw_ArgumentNilError('ACollection1');
  if not Assigned(ACollection2) then
    ExceptionHelper.Throw_ArgumentNilError('ACollection2');
  { Rules ... }
  inherited Create(ACollection1.ElementRules);
  { Assign internals }
  FCollection1 := ACollection1;
  KeepObjectAlive(FCollection1);
  FCollection2 := ACollection2;
end;
destructor TEnexUnionCollection<T>.Destroy;
begin
  { Delete the enumerable if required }
  ReleaseObject(FCollection1, false);
  inherited;
end;
function TEnexUnionCollection<T>.GetEnumerator: IEnumerator<T>;
begin
  { Create enumerator }
  Result := TEnumerator.Create(Self);
end;
{ TEnexUnionCollection<T>.TEnumerator }
constructor TEnexUnionCollection<T>.TEnumerator.Create(const ACollection: TEnexUnionCollection<T>);
begin
  { Initialize }
  FCollection := ACollection;
  KeepObjectAlive(FCollection);
  FEnumerator1 := ACollection.FCollection1.GetEnumerator();
  FEnumerator2 := ACollection.FCollection2.GetEnumerator();
  { Create an internal set }
  FSet := THashSet<T>.Create(ACollection.FCollection1.ElementRules);
end;
destructor TEnexUnionCollection<T>.TEnumerator.Destroy;
begin
  ReleaseObject(FCollection);
  inherited;
end;
function TEnexUnionCollection<T>.TEnumerator.GetCurrent: T;
begin
  { Pass the first and then the last }
  if Assigned(FEnumerator1) then
    Result := FEnumerator1.Current
  else
    Result := FEnumerator2.Current;
end;
function TEnexUnionCollection<T>.TEnumerator.MoveNext: Boolean;
begin
  if Assigned(FEnumerator1) then
  begin
    { Iterate over 1 }
    Result := FEnumerator1.MoveNext();
    { Succesefully iterated collection 1 }
    if Result then
    begin
      { Add the element to the set }
      FSet.Add(FEnumerator1.Current);
      Exit;
    end;
    { We've reached the bottom of 1 }
    FEnumerator1 := nil;
  end;
  { Continue until we find what we need or we get to the bottom }
  while True do
  begin
    { Iterate over 2 now }
    Result := FEnumerator2.MoveNext();
    { Exit on bad result }
    if not Result then
      Exit;
    { Exit if the element is good }
    if not FSet.Contains(FEnumerator2.Current) then
    begin
      FSet.Add(FEnumerator2.Current);
      Exit;
    end;
  end;
end;
{ TEnexExclusionCollection<T> }
constructor TEnexExclusionCollection<T>.Create(
  const ACollection1: TEnexCollection<T>; const ACollection2: IEnexCollection<T>);
begin
  { Check arguments }
  if not Assigned(ACollection1) then
    ExceptionHelper.Throw_ArgumentNilError('ACollection1');
  if not Assigned(ACollection2) then
    ExceptionHelper.Throw_ArgumentNilError('ACollection2');
  { Rules ... }
  inherited Create(ACollection1.ElementRules);
  { Assign internals }
  FCollection1 := ACollection1;
  KeepObjectAlive(FCollection1);
  FCollection2 := ACollection2;
end;
destructor TEnexExclusionCollection<T>.Destroy;
begin
  { Delete the enumerable if required }
  ReleaseObject(FCollection1, false);
  inherited;
end;
function TEnexExclusionCollection<T>.GetEnumerator: IEnumerator<T>;
begin
  { Create enumerator }
  Result := TEnumerator.Create(Self);
end;
{ TEnexExclusionCollection<T>.TEnumerator }
constructor TEnexExclusionCollection<T>.TEnumerator.Create(const ACollection: TEnexExclusionCollection<T>);
begin
  { Initialize }
  FCollection := ACollection;
  KeepObjectAlive(FCollection);
  FEnumerator := ACollection.FCollection1.GetEnumerator();
  { Create an internal set }
  FSet := THashSet<T>.Create(ACollection.FCollection1.ElementRules, ACollection.FCollection2);
end;
destructor TEnexExclusionCollection<T>.TEnumerator.Destroy;
begin
  ReleaseObject(FCollection);
  inherited;
end;
function TEnexExclusionCollection<T>.TEnumerator.GetCurrent: T;
begin
  { Pass 1's enumerator }
  Result := FEnumerator.Current;
end;
function TEnexExclusionCollection<T>.TEnumerator.MoveNext: Boolean;
begin
  { Continue until we find what we need or we get to the bottom }
  while True do
  begin
    { Iterate over 1 }
    Result := FEnumerator.MoveNext();
    { Exit on bad result }
    if not Result then
      Exit;
    { Exit if the element is good }
    if not FSet.Contains(FEnumerator.Current) then
      Exit;
  end;
end;
{ TEnexIntersectionCollection<T> }
constructor TEnexIntersectionCollection<T>.Create(
  const ACollection1: TEnexCollection<T>; const ACollection2: IEnexCollection<T>);
begin
  { Check arguments }
  if not Assigned(ACollection1) then
    ExceptionHelper.Throw_ArgumentNilError('ACollection1');
  if not Assigned(ACollection2) then
    ExceptionHelper.Throw_ArgumentNilError('ACollection2');
  { Rules ... }
  inherited Create(ACollection1.ElementRules);
  { Assign internals }
  FCollection1 := ACollection1;
  KeepObjectAlive(FCollection1);
  FCollection2 := ACollection2;
end;
destructor TEnexIntersectionCollection<T>.Destroy;
begin
  { Delete the enumerable if required }
  ReleaseObject(FCollection1, false);
  inherited;
end;
function TEnexIntersectionCollection<T>.GetEnumerator: IEnumerator<T>;
begin
  { Create enumerator }
  Result := TEnumerator.Create(Self);
end;
{ Collection.EnexIntersectionCollection<T>.TEnumerator }
constructor TEnexIntersectionCollection<T>.TEnumerator .Create(const ACollection: TEnexIntersectionCollection<T>);
begin
  { Initialize }
  FCollection := ACollection;
  KeepObjectAlive(FCollection);
  FEnumerator := ACollection.FCollection1.GetEnumerator();
  { Create an internal set }
  FSet := THashSet<T>.Create(ACollection.FCollection1.ElementRules, ACollection.FCollection2);
end;
destructor TEnexIntersectionCollection<T>.TEnumerator .Destroy;
begin
  ReleaseObject(FCollection);
  inherited;
end;
function TEnexIntersectionCollection<T>.TEnumerator .GetCurrent: T;
begin
  { Pass 1's enumerator }
  Result := FEnumerator.Current;
end;
function TEnexIntersectionCollection<T>.TEnumerator .MoveNext: Boolean;
begin
  { Continue until we find what we need or we get to the bottom }
  while True do
  begin
    { Iterate over 1 }
    Result := FEnumerator.MoveNext();
    { Exit on bad result }
    if not Result then
      Exit;
    { Exit if the element is good }
    if FSet.Contains(FEnumerator.Current) then
      Exit;
  end;
end;
{ TEnexRangeCollection<T> }
constructor TEnexRangeCollection<T>.Create(const ACollection: TEnexCollection<T>; const AStart, AEnd: NativeInt);
begin
  if AStart < 0 then
    ExceptionHelper.Throw_ArgumentOutOfRangeError('AStart');
  if AEnd < 0 then
    ExceptionHelper.Throw_ArgumentOutOfRangeError('AEnd');
  { Check arguments }
  if not Assigned(ACollection) then
    ExceptionHelper.Throw_ArgumentNilError('ACollection');
  { Rules ... }
  inherited Create(ACollection.ElementRules);
  { Assign internals }
  FCollection := ACollection;
  KeepObjectAlive(FCollection);
  FStart := AStart;
  FEnd := AEnd;
end;
destructor TEnexRangeCollection<T>.Destroy;
begin
  { Delete the enumerable if required }
  ReleaseObject(FCollection, false);
  inherited;
end;
function TEnexRangeCollection<T>.GetEnumerator: IEnumerator<T>;
begin
  { Create the enumerator }
  Result := TEnumerator.Create(Self);
end;
{ TEnexRangeCollection<T>.TEnumerator }
constructor TEnexRangeCollection<T>.TEnumerator.Create(const ACollection: TEnexRangeCollection<T>);
begin
  { Initialize }
  FCollection := ACollection;
  KeepObjectAlive(FCollection);
  FEnumerator := ACollection.FCollection.GetEnumerator();
  FIdx  := 0;
end;
destructor TEnexRangeCollection<T>.TEnumerator.Destroy;
begin
  ReleaseObject(FCollection);
  inherited;
end;
function TEnexRangeCollection<T>.TEnumerator.GetCurrent: T;
begin
  { PAss the current in the sub-enum }
  Result := FEnumerator.Current;
end;
function TEnexRangeCollection<T>.TEnumerator.MoveNext: Boolean;
begin
  { Skip the required amount of elements }
  if (FIdx <= FCollection.FStart) then
  begin
    while (FIdx <= FCollection.FStart) do
    begin
      { Move cursor }
      Result := FEnumerator.MoveNext();
      if not Result then
        Exit;
      Inc(FIdx);
    end;
  end else
  begin
    { Check if we're finished }
    if (FIdx > FCollection.FEnd) then
      Exit(false);
    { Move the cursor next in the sub-enum, and increase index }
    Result := FEnumerator.MoveNext();
    Inc(FIdx);
  end;
end;
{ TEnexDistinctCollection<T> }
constructor TEnexDistinctCollection<T>.Create(const ACollection: TEnexCollection<T>);
begin
  { Check arguments }
  if not Assigned(ACollection) then
    ExceptionHelper.Throw_ArgumentNilError('ACollection');
  inherited Create(ACollection.ElementRules);
  { Assign internals }
  FCollection := ACollection;
  KeepObjectAlive(FCollection);
end;
destructor TEnexDistinctCollection<T>.Destroy;
begin
  { Delete the enumerable if required }
  ReleaseObject(FCollection, false);
  inherited;
end;
function TEnexDistinctCollection<T>.GetEnumerator: IEnumerator<T>;
begin
  { Create an enumerator }
  Result := TEnumerator.Create(Self);
end;
{ TEnexDistinctCollection<T>.TEnumerator }
constructor TEnexDistinctCollection<T>.TEnumerator.Create(const ACollection: TEnexDistinctCollection<T>);
begin
  { Initialize }
  FCollection := ACollection;
  KeepObjectAlive(FCollection);
  FEnumerator := ACollection.FCollection.GetEnumerator();
  { Create an internal set }
  FSet := THashSet<T>.Create(ACollection.FCollection.ElementRules);
end;
destructor TEnexDistinctCollection<T>.TEnumerator.Destroy;
begin
  ReleaseObject(FCollection);
  inherited;
end;
function TEnexDistinctCollection<T>.TEnumerator.GetCurrent: T;
begin
  { Get from sub-enum }
  Result := FEnumerator.Current;
end;
function TEnexDistinctCollection<T>.TEnumerator.MoveNext: Boolean;
begin
  while True do
  begin
    { Iterate }
    Result := FEnumerator.MoveNext;
    if not Result then
      Exit;
    { If the item is distinct, add it to set and continue }
    if not FSet.Contains(FEnumerator.Current) then
    begin
      FSet.Add(FEnumerator.Current);
      Exit;
    end;
  end;
end;
{ TEnexFillCollection<T> }
function TEnexFillCollection<T>.Aggregate(const AAggregator: TFunc<T, T, T>): T;
var
  I: NativeInt;
begin
  { Check arguments }
  if not Assigned(AAggregator) then
    ExceptionHelper.Throw_ArgumentNilError('AAggregator');
  if FCount = 0 then
    ExceptionHelper.Throw_CollectionEmptyError();
  { Select the first element as comparison base }
  Result := FElement;
  { Iterate over the last N - 1 elements }
  for I := 1 to FCount - 1 do
  begin
    { Aggregate a value }
    Result := AAggregator(Result, FElement);
  end;
end;
function TEnexFillCollection<T>.AggregateOrDefault(const AAggregator: TFunc<T, T, T>; const ADefault: T): T;
var
  I: NativeInt;
begin
  { Check arguments }
  if not Assigned(AAggregator) then
    ExceptionHelper.Throw_ArgumentNilError('AAggregator');
  if FCount = 0 then
    Exit(ADefault);
  { Select the first element as comparison base }
  Result := FElement;
  { Iterate over the last N - 1 elements }
  for I := 1 to FCount - 1 do
  begin
    { Aggregate a value }
    Result := AAggregator(Result, FElement);
  end;
end;
function TEnexFillCollection<T>.All(const APredicate: TFunc<T, Boolean>): Boolean;
begin
  if not Assigned(APredicate) then
    ExceptionHelper.Throw_ArgumentNilError('APredicate');
  if not APredicate(FElement) then
    Result := false
  else
    Result := true;
end;
function TEnexFillCollection<T>.Any(const APredicate: TFunc<T, Boolean>): Boolean;
begin
  if not Assigned(APredicate) then
    ExceptionHelper.Throw_ArgumentNilError('APredicate');
  if APredicate(FElement) then
    Result := true
  else
    Result := false;
end;
constructor TEnexFillCollection<T>.Create(const AElement: T; const ACount: NativeInt; const ARules: TRules<T>);
begin
  if ACount <= 0 then
    ExceptionHelper.Throw_ArgumentOutOfRangeError('ACount');
  { Install the type }
  inherited Create(ARules);
  { Copy values in }
  FCount := ACount;
  FElement := AElement;
end;
function TEnexFillCollection<T>.ElementAt(const AIndex: NativeInt): T;
begin
  if (AIndex = FCount) or (AIndex < 0) then
    ExceptionHelper.Throw_ArgumentOutOfRangeError('AIndex');
  Result := FElement;
end;
function TEnexFillCollection<T>.ElementAtOrDefault(const AIndex: NativeInt; const ADefault: T): T;
begin
  if (AIndex = FCount) or (AIndex < 0) then
    Result := ADefault
  else
    Result := FElement;
end;
function TEnexFillCollection<T>.Empty: Boolean;
begin
  Result := (FCount = 0);
end;
function TEnexFillCollection<T>.EqualsTo(const ACollection: IEnumerable<T>): Boolean;
var
  LValue: T;
  I: NativeInt;
begin
  I := 0;
  for LValue in ACollection do
  begin
    if I >= FCount then
      Exit(false);
    if not ElementsAreEqual(FElement, LValue) then
      Exit(false);
    Inc(I);
  end;
  if I < FCount then
    Exit(false);
  Result := true;
end;
function TEnexFillCollection<T>.First: T;
begin
  if FCount = 0 then
    ExceptionHelper.Throw_CollectionEmptyError();
  Result := FElement;
end;
function TEnexFillCollection<T>.FirstOrDefault(const ADefault: T): T;
begin
  if FCount = 0 then
    Result := ADefault
  else
    Result := FElement;
end;
function TEnexFillCollection<T>.GetCount: NativeInt;
begin
  Result := FCount;
end;
function TEnexFillCollection<T>.GetEnumerator: IEnumerator<T>;
begin
  { Create an enumerator }
  Result := TEnumerator.Create(Self);
end;
function TEnexFillCollection<T>.Last: T;
begin
  if FCount = 0 then
    ExceptionHelper.Throw_CollectionEmptyError();
  Result := FElement;
end;
function TEnexFillCollection<T>.LastOrDefault(const ADefault: T): T;
begin
  if FCount = 0 then
    Result := ADefault
  else
    Result := FElement;
end;
function TEnexFillCollection<T>.Max: T;
begin
  if FCount = 0 then
    ExceptionHelper.Throw_CollectionEmptyError();
  Result := FElement;
end;
function TEnexFillCollection<T>.Min: T;
begin
  if FCount = 0 then
    ExceptionHelper.Throw_CollectionEmptyError();
  Result := FElement;
end;
function TEnexFillCollection<T>.Single: T;
begin
  if FCount = 0 then
    ExceptionHelper.Throw_CollectionEmptyError()
  else if FCount = 1 then
    Result := FElement
  else
    ExceptionHelper.Throw_CollectionHasMoreThanOneElement();
end;
function TEnexFillCollection<T>.SingleOrDefault(const ADefault: T): T;
begin
  if FCount = 0 then
    Result := ADefault
  else if FCount = 1 then
    Result := FElement
  else
    ExceptionHelper.Throw_CollectionHasMoreThanOneElement();
end;
{ TEnexFillCollection<T>.TEnumerator }
constructor TEnexFillCollection<T>.TEnumerator.Create(const ACollection: TEnexFillCollection<T>);
begin
  FCollection := ACollection;
  KeepObjectAlive(FCollection);
  FCount := 0;
end;
destructor TEnexFillCollection<T>.TEnumerator.Destroy;
begin
  ReleaseObject(FCollection);
  inherited;
end;
function TEnexFillCollection<T>.TEnumerator.GetCurrent: T;
begin
  { Pass the element }
  Result := FCollection.FElement;
end;
function TEnexFillCollection<T>.TEnumerator.MoveNext: Boolean;
begin
  { Check for end }
  Result := (FCount < FCollection.FCount);
  if not Result then
    Exit;
  Inc(FCount);
end;
{ TEnexSkipCollection<T> }
constructor TEnexSkipCollection<T>.Create(const ACollection: TEnexCollection<T>; const ACount: NativeInt);
begin
  { Check parameters }
  if ACount <= 0 then
    ExceptionHelper.Throw_ArgumentOutOfRangeError('ACount');
  { Check arguments }
  if not Assigned(ACollection) then
    ExceptionHelper.Throw_ArgumentNilError('ACollection');
  { Installing the element type }
  inherited Create(ACollection.ElementRules);
  { Assign internals }
  FCollection := ACollection;
  KeepObjectAlive(FCollection);
  FCount := ACount;
end;
destructor TEnexSkipCollection<T>.Destroy;
begin
  { Delete the enumerable if required }
  ReleaseObject(FCollection, false);
  inherited;
end;
function TEnexSkipCollection<T>.GetEnumerator: IEnumerator<T>;
begin
  { Create the enumerator }
  Result := TEnumerator.Create(Self);
end;
{ TEnexSkipCollection<T>.TEnumerator }
constructor TEnexSkipCollection<T>.TEnumerator.Create(const ACollection: TEnexSkipCollection<T>);
begin
  { Initialize }
  FCollection := ACollection;
  KeepObjectAlive(FCollection);
  FEnumerator := ACollection.FCollection.GetEnumerator();
  FIdx  := 0;
end;
destructor TEnexSkipCollection<T>.TEnumerator.Destroy;
begin
  ReleaseObject(FCollection);
  inherited;
end;
function TEnexSkipCollection<T>.TEnumerator.GetCurrent: T;
begin
  { PAss the current in the sub-enum }
  Result := FEnumerator.Current;
end;
function TEnexSkipCollection<T>.TEnumerator.MoveNext: Boolean;
begin
  { Skip the required amount of elements }
  if (FIdx < FCollection.FCount) then
  begin
    while (FIdx < FCollection.FCount) do
    begin
      { Move cursor }
      Result := FEnumerator.MoveNext();
      if not Result then
        Exit;
      Inc(FIdx);
    end;
  end;
  Result := FEnumerator.MoveNext(); { Move the cursor next in the sub-enum }
end;
{ TEnexTakeCollection<T> }
constructor TEnexTakeCollection<T>.Create(const ACollection: TEnexCollection<T>; const ACount: NativeInt);
begin
  { Check parameters }
  if ACount <= 0 then
    ExceptionHelper.Throw_ArgumentOutOfRangeError('ACount');
  { Check arguments }
  if not Assigned(ACollection) then
    ExceptionHelper.Throw_ArgumentNilError('ACollection');
  { Installing the element type }
  inherited Create(ACollection.ElementRules);
  { Assign internals }
  FCollection := ACollection;
  KeepObjectAlive(FCollection);
  FCount := ACount;
end;
destructor TEnexTakeCollection<T>.Destroy;
begin
  { Delete the enumerable if required }
  ReleaseObject(FCollection, false);
  inherited;
end;
function TEnexTakeCollection<T>.GetEnumerator: IEnumerator<T>;
begin
  { Create the enumerator }
  Result := TEnumerator.Create(Self);
end;
{ TEnexTakeCollection<T>.TEnumerator }
constructor TEnexTakeCollection<T>.TEnumerator.Create(const ACollection: TEnexTakeCollection<T>);
begin
  { Initialize }
  FCollection := ACollection;
  KeepObjectAlive(FCollection);
  FEnumerator := ACollection.FCollection.GetEnumerator();
  FIdx  := 0;
end;
destructor TEnexTakeCollection<T>.TEnumerator.Destroy;
begin
  ReleaseObject(FCollection);
  inherited;
end;
function TEnexTakeCollection<T>.TEnumerator.GetCurrent: T;
begin
  { Pass the current in the sub-enum }
  Result := FEnumerator.Current;
end;
function TEnexTakeCollection<T>.TEnumerator.MoveNext: Boolean;
begin
  { Check if we're finished}
  if (FIdx >= FCollection.FCount) then
    Exit(false);
  { Move the cursor next in the sub-enum, and increase index }
  Result := FEnumerator.MoveNext();
  Inc(FIdx);
end;
{ TEnexTakeWhileCollection<T> }
constructor TEnexTakeWhileCollection<T>.Create(const ACollection: TEnexCollection<T>; const APredicate: TFunc<T, Boolean>);
begin
  { Check arguments }
  if not Assigned(APredicate) then
    ExceptionHelper.Throw_ArgumentNilError('APredicate');
  if not Assigned(ACollection) then
    ExceptionHelper.Throw_ArgumentNilError('ACollection');
  { Install the type }
  inherited Create(ACollection.ElementRules);
  { Assign internals }
  FCollection := ACollection;
  KeepObjectAlive(FCollection);
  FPredicate := APredicate;
end;
destructor TEnexTakeWhileCollection<T>.Destroy;
begin
  { Delete the enumerable if required }
  ReleaseObject(FCollection, false);
  inherited;
end;
function TEnexTakeWhileCollection<T>.GetEnumerator: IEnumerator<T>;
begin
  { Generate an enumerator }
  Result := TEnumerator.Create(Self);
end;
{ TEnexTakeWhileCollection<T>.TEnumerator }
constructor TEnexTakeWhileCollection<T>.TEnumerator.Create(const ACollection: TEnexTakeWhileCollection<T>);
begin
  { Initialize }
  FCollection := ACollection;
  KeepObjectAlive(FCollection);
  FEnumerator:= ACollection.FCollection.GetEnumerator();
end;
destructor TEnexTakeWhileCollection<T>.TEnumerator.Destroy;
begin
  ReleaseObject(FCollection);
  inherited;
end;
function TEnexTakeWhileCollection<T>.TEnumerator.GetCurrent: T;
begin
  { Get current element of the "sub-enumerable" object }
  Result := FEnumerator.Current;
end;
function TEnexTakeWhileCollection<T>.TEnumerator.MoveNext: Boolean;
begin
  Result := FEnumerator.MoveNext;
  { Terminate on sub-enum termination }
  if not Result then
    Exit;
  { When the condition is not met, stop iterating! }
  if not FCollection.FPredicate(FEnumerator.Current) then
    Exit(false);
end;
{ TEnexSkipWhileCollection<T> }
constructor TEnexSkipWhileCollection<T>.Create(const ACollection: TEnexCollection<T>; const APredicate: TFunc<T, Boolean>);
begin
  { Check arguments }
  if not Assigned(APredicate) then
    ExceptionHelper.Throw_ArgumentNilError('APredicate');
  if not Assigned(ACollection) then
    ExceptionHelper.Throw_ArgumentNilError('ACollection');
  { Install the type }
  inherited Create(ACollection.ElementRules);
  { Assign internals }
  FCollection := ACollection;
  KeepObjectAlive(FCollection);
  FPredicate := APredicate;
end;
destructor TEnexSkipWhileCollection<T>.Destroy;
begin
  { Delete the enumerable if required }
  ReleaseObject(FCollection, false);
  inherited;
end;
function TEnexSkipWhileCollection<T>.GetEnumerator: IEnumerator<T>;
begin
  { Generate an enumerator }
  Result := TEnumerator.Create(Self);
end;
{ TEnexSkipWhileCollection<T>.TEnumerator }
constructor TEnexSkipWhileCollection<T>.TEnumerator.Create(const ACollection: TEnexSkipWhileCollection<T>);
begin
  { Initialize }
  FCollection := ACollection;
  KeepObjectAlive(FCollection);
  FEnumerator := ACollection.FCollection.GetEnumerator();
  FStop := false;
end;
destructor TEnexSkipWhileCollection<T>.TEnumerator.Destroy;
begin
  ReleaseObject(FCollection);
  inherited;
end;
function TEnexSkipWhileCollection<T>.TEnumerator.GetCurrent: T;
begin
  { Get current element of the "sub-enumerable" object }
  Result := FEnumerator.Current;
end;
function TEnexSkipWhileCollection<T>.TEnumerator.MoveNext: Boolean;
begin
  { Iterate until given condition is met on an element }
  if not FStop then
  begin
    while not FStop do
    begin
      Result := FEnumerator.MoveNext;
      { Terminate on sub-enum termination }
      if not Result then
        Exit;
      { When condition is met, move next }
      if FCollection.FPredicate(FEnumerator.Current) then
        Continue;
      { Mark as skipped }
      FStop := true;
    end;
  end else
    Result := FEnumerator.MoveNext;
end;
{ TEnexGroupByCollection<T, TGroup> }
constructor TEnexGroupByCollection<T, TBy>.Create(
  const ACollection: TEnexCollection<T>; const ASelector: TFunc<T, TBy>);
begin
  { Check arguments }
  if not Assigned(ASelector) then
    ExceptionHelper.Throw_ArgumentNilError('ASelector');
  if not Assigned(ACollection) then
    ExceptionHelper.Throw_ArgumentNilError('ACollection');
  { Install the type (some default type) }
  inherited Create();
  { Assign internals }
  FCollection := ACollection;
  KeepObjectAlive(FCollection);
  FSelector := ASelector;
end;
destructor TEnexGroupByCollection<T, TBy>.Destroy;
begin
  { Delete the enumerable if required }
  ReleaseObject(FCollection, false);
  inherited;
end;
function TEnexGroupByCollection<T, TBy>.GetEnumerator: IEnumerator<IEnexGroupingCollection<TBy, T>>;
var
  LDictionary: IDictionary<TBy, IList<T>>;
  LList: IList<T>;
  LSrcEnumerator: IEnumerator<T>;
  LDictEnumerator: IEnumerator<TPair<TBy, IList<T>>>;
  LGroup: TBy;
  LOutList: IList<IEnexGroupingCollection<TBy, T>>;
  LGrouping: TEnexGroupingCollection;
  LGroupingIntf: IEnexGroupingCollection<TBy, T>;
begin
  { Initialize the dictionary (need one that preserves the input order) }
  LDictionary := TLinkedDictionary<TBy, IList<T>>.Create();
  { Obtain the source enumerator }
  LSrcEnumerator := FCollection.GetEnumerator();
  while LSrcEnumerator.MoveNext() do
  begin
    LGroup := FSelector(LSrcEnumerator.Current);
    { Try to get the list of groupet input elements }
    if not LDictionary.TryGetValue(LGroup, LList) then
    begin
      LList := TList<T>.Create();
      LDictionary.Add(LGroup, LList);
    end;
    { Add the element that was grouped into the list, and move on ... }
    LList.Add(LSrcEnumerator.Current);
  end;
  { Build result and such things }
  LOutList := TList<IEnexGroupingCollection<TBy, T>>.Create();
  { Get the dictionary enumerator and build output }
  LDictEnumerator := LDictionary.GetEnumerator();
  while LDictEnumerator.MoveNext() do
  begin
    { Initialize the grouping structure }
    LGrouping := TEnexGroupingCollection.Create;
    LGrouping.FBy := LDictEnumerator.Current.Key;
    LGrouping.FList := LDictEnumerator.Current.Value;
    LGroupingIntf := LGrouping;
    { Place it into output }
    LOutList.Add(LGroupingIntf);
  end;
  LDictEnumerator := nil;
  LDictionary := nil;
  { Finally, provide the enumerator }
  Result := LOutList.GetEnumerator();
end;
{ TEnexGroupByCollection<T, TKey>.TEnexGroupingCollection }
function TEnexGroupByCollection<T, TBy>.TEnexGroupingCollection.Aggregate(const AAggregator: TFunc<T, T, T>): T;
begin
  Result := FList.Aggregate(AAggregator);
end;
function TEnexGroupByCollection<T, TBy>.TEnexGroupingCollection.AggregateOrDefault(const AAggregator: TFunc<T, T, T>; const ADefault: T): T;
begin
  Result := FList.AggregateOrDefault(AAggregator, ADefault);
end;
function TEnexGroupByCollection<T, TBy>.TEnexGroupingCollection.All(const APredicate: TFunc<T, Boolean>): Boolean;
begin
  Result := FList.All(APredicate);
end;
function TEnexGroupByCollection<T, TBy>.TEnexGroupingCollection.Any(const APredicate: TFunc<T, Boolean>): Boolean;
begin
  Result := FList.Any(APredicate);
end;
procedure TEnexGroupByCollection<T, TBy>.TEnexGroupingCollection.CopyTo(var AArray: array of T; const AStartIndex: NativeInt);
begin
  FList.CopyTo(AArray, AStartIndex);
end;
function TEnexGroupByCollection<T, TBy>.TEnexGroupingCollection.ElementAt(const AIndex: NativeInt): T;
begin
  Result := FList.ElementAt(AIndex);
end;
function TEnexGroupByCollection<T, TBy>.TEnexGroupingCollection.ElementAtOrDefault(const AIndex: NativeInt; const ADefault: T): T;
begin
  Result := FList.ElementAtOrDefault(AIndex, ADefault);
end;
function TEnexGroupByCollection<T, TBy>.TEnexGroupingCollection.Empty: Boolean;
begin
  Result := FList.Empty;
end;
function TEnexGroupByCollection<T, TBy>.TEnexGroupingCollection.EqualsTo(const ACollection: IEnumerable<T>): Boolean;
begin
  Result := FList.EqualsTo(ACollection);
end;
function TEnexGroupByCollection<T, TBy>.TEnexGroupingCollection.First: T;
begin
  Result := FList.First;
end;
function TEnexGroupByCollection<T, TBy>.TEnexGroupingCollection.FirstOrDefault(const ADefault: T): T;
begin
  Result := FList.FirstOrDefault(ADefault);
end;
function TEnexGroupByCollection<T, TBy>.TEnexGroupingCollection.GetCount: NativeInt;
begin
  Result := FList.Count;
end;
function TEnexGroupByCollection<T, TBy>.TEnexGroupingCollection.GetEnumerator: IEnumerator<T>;
begin
  Result := FList.GetEnumerator();
end;
function TEnexGroupByCollection<T, TBy>.TEnexGroupingCollection.GetKey: TBy;
begin
  Result := FBy;
end;
function TEnexGroupByCollection<T, TBy>.TEnexGroupingCollection.Last: T;
begin
  Result := FList.Last;
end;
function TEnexGroupByCollection<T, TBy>.TEnexGroupingCollection.LastOrDefault(const ADefault: T): T;
begin
  Result := FList.LastOrDefault(ADefault);
end;
function TEnexGroupByCollection<T, TBy>.TEnexGroupingCollection.Max: T;
begin
  Result := FList.Max;
end;
function TEnexGroupByCollection<T, TBy>.TEnexGroupingCollection.Min: T;
begin
  Result := FList.Min;
end;
function TEnexGroupByCollection<T, TBy>.TEnexGroupingCollection.Single: T;
begin
  Result := FList.Single;
end;
function TEnexGroupByCollection<T, TBy>.TEnexGroupingCollection.SingleOrDefault(const ADefault: T): T;
begin
  Result := FList.SingleOrDefault(ADefault);
end;
{ TEnexSelectKeysCollection<TKey, TValue> }
constructor TEnexSelectKeysCollection<TKey, TValue>.Create(const ACollection: TEnexAssociativeCollection<TKey, TValue>);
begin
  { Check arguments }
  if not Assigned(ACollection) then
    ExceptionHelper.Throw_ArgumentNilError('ACollection');
  { Install the type }
  inherited Create(ACollection.KeyRules);
  { Assign internals }
  FCollection := ACollection;
  KeepObjectAlive(FCollection);
end;
destructor TEnexSelectKeysCollection<TKey, TValue>.Destroy;
begin
  { Delete the enumerable if required }
  ReleaseObject(FCollection, false);
  inherited;
end;
function TEnexSelectKeysCollection<TKey, TValue>.GetCount: NativeInt;
begin
  Result := FCollection.GetCount();
end;
function TEnexSelectKeysCollection<TKey, TValue>.GetEnumerator: IEnumerator<TKey>;
begin
  { Generate an enumerator }
  Result := TEnumerator.Create(Self);
end;
{ TEnexSelectKeysCollection<TKey, TValue>.TEnumerator }
constructor TEnexSelectKeysCollection<TKey, TValue>.TEnumerator.Create(
  const ACollection: TEnexSelectKeysCollection<TKey, TValue>);
begin
  { Initialize }
  FCollection := ACollection;
  KeepObjectAlive(FCollection);
  FEnumerator:= ACollection.FCollection.GetEnumerator();
  FCurrent := default(TKey);
end;
destructor TEnexSelectKeysCollection<TKey, TValue>.TEnumerator.Destroy;
begin
  ReleaseObject(FCollection);
  inherited;
end;
function TEnexSelectKeysCollection<TKey, TValue>.TEnumerator.GetCurrent: TKey;
begin
  { Get current element of the "sub-enumerable" object }
  Result := FCurrent;
end;
function TEnexSelectKeysCollection<TKey, TValue>.TEnumerator.MoveNext: Boolean;
begin
  { Next iteration }
  Result := FEnumerator.MoveNext;
  { Terminate on sub-enum termination }
  if not Result then
    Exit;
  { Return the next "selected" key }
  FCurrent := FEnumerator.Current.Key;
end;
{ TEnexSelectValuesCollection<TKey, TValue> }
constructor TEnexSelectValuesCollection<TKey, TValue>.Create(
  const ACollection: TEnexAssociativeCollection<TKey, TValue>);
begin
  { Check arguments }
  if not Assigned(ACollection) then
    ExceptionHelper.Throw_ArgumentNilError('ACollection');
  { Install the type }
  inherited Create(ACollection.ValueRules);
  { Assign internals }
  FCollection := ACollection;
  KeepObjectAlive(FCollection);
end;
destructor TEnexSelectValuesCollection<TKey, TValue>.Destroy;
begin
  { Delete the enumerable if required }
  ReleaseObject(FCollection, false);
  inherited;
end;
function TEnexSelectValuesCollection<TKey, TValue>.GetCount: NativeInt;
begin
  Result := FCollection.GetCount();
end;
function TEnexSelectValuesCollection<TKey, TValue>.GetEnumerator: IEnumerator<TValue>;
begin
  { Generate an enumerator }
  Result := TEnumerator.Create(Self);
end;
{ TEnexSelectValuesCollection<TKey, TValue>.TEnumerator }
constructor TEnexSelectValuesCollection<TKey, TValue>.TEnumerator.Create(
  const ACollection: TEnexSelectValuesCollection<TKey, TValue>);
begin
  { Initialize }
  FCollection := ACollection;
  KeepObjectAlive(FCollection);
  FEnumerator:= ACollection.FCollection.GetEnumerator();
  FCurrent := default(TValue);
end;
destructor TEnexSelectValuesCollection<TKey, TValue>.TEnumerator.Destroy;
begin
  ReleaseObject(FCollection);
  inherited;
end;
function TEnexSelectValuesCollection<TKey, TValue>.TEnumerator.GetCurrent: TValue;
begin
  { Get current element of the "sub-enumerable" object }
  Result := FCurrent;
end;
function TEnexSelectValuesCollection<TKey, TValue>.TEnumerator.MoveNext: Boolean;
begin
  { Next iteration }
  Result := FEnumerator.MoveNext;
  { Terminate on sub-enum termination }
  if not Result then
    Exit;
  { Return the next "selected" key }
  FCurrent := FEnumerator.Current.Value;
end;
{ TEnexAssociativeWhereCollection<TKey, TValue> }
constructor TEnexAssociativeWhereCollection<TKey, TValue>.Create(
  const ACollection: TEnexAssociativeCollection<TKey, TValue>;
  const APredicate: TFunc<TKey, TValue, Boolean>;
  const AInvertResult: Boolean);
begin
  { Check arguments }
  if not Assigned(APredicate) then
    ExceptionHelper.Throw_ArgumentNilError('APredicate');
  if not Assigned(ACollection) then
    ExceptionHelper.Throw_ArgumentNilError('ACollection');
  { Install types }
  inherited Create(ACollection.KeyRules, ACollection.ValueRules);
  { Assign internals }
  FCollection := ACollection;
  KeepObjectAlive(FCollection);
  FPredicate := APredicate;
  FInvertResult := AInvertResult;
end;
destructor TEnexAssociativeWhereCollection<TKey, TValue>.Destroy;
begin
  { Delete the enumerable if required }
  ReleaseObject(FCollection, false);
  inherited;
end;
function TEnexAssociativeWhereCollection<TKey, TValue>.GetEnumerator: IEnumerator<TPair<TKey, TValue>>;
begin
  { Generate an enumerator }
  Result := TEnumerator.Create(Self);
end;
{ TEnexAssociativeWhereCollection<TKey, TValue>.TEnumerator }
constructor TEnexAssociativeWhereCollection<TKey, TValue>.TEnumerator.Create(
  const ACollection: TEnexAssociativeWhereCollection<TKey, TValue>);
begin
  { Initialize }
  FCollection := ACollection;
  KeepObjectAlive(FCollection);
  FEnumerator := ACollection.FCollection.GetEnumerator();
end;
destructor TEnexAssociativeWhereCollection<TKey, TValue>.TEnumerator.Destroy;
begin
  ReleaseObject(FCollection);
  inherited;
end;
function TEnexAssociativeWhereCollection<TKey, TValue>.TEnumerator.GetCurrent: TPair<TKey, TValue>;
begin
  { Get current element of the "sub-enumerable" object }
  Result := FEnumerator.Current;
end;
function TEnexAssociativeWhereCollection<TKey, TValue>.TEnumerator.MoveNext: Boolean;
begin
  { Iterate until given condition is met on an element }
  while True do
  begin
    Result := FEnumerator.MoveNext;
    { Terminate on sub-enum termination }
    if not Result then
      Exit;
    { Check whether the current element meets the condition and exit }
    { ... otherwise continue to the next iteration }
    if FCollection.FPredicate(FEnumerator.Current.Key, FEnumerator.Current.Value) xor FCollection.FInvertResult then
      Exit;
  end;
end;
{ TCollection.EnexAssociativeDistinctByKeysCollection<TKey, TValue> }
constructor TEnexAssociativeDistinctByKeysCollection<TKey, TValue>.Create(
  const ACollection: TEnexAssociativeCollection<TKey, TValue>);
begin
  { Check arguments }
  if not Assigned(ACollection) then
    ExceptionHelper.Throw_ArgumentNilError('ACollection');
  { Install types }
  inherited Create(ACollection.KeyRules, ACollection.ValueRules);
  { Assign internals }
  FCollection := ACollection;
  KeepObjectAlive(FCollection);
end;
destructor TEnexAssociativeDistinctByKeysCollection<TKey, TValue>.Destroy;
begin
  { Delete the enumerable if required }
  ReleaseObject(FCollection, false);
  inherited;
end;
function TEnexAssociativeDistinctByKeysCollection<TKey, TValue>.GetEnumerator: IEnumerator<TPair<TKey, TValue>>;
begin
  { Create an enumerator }
  Result := TEnumerator.Create(Self);
end;
{ TEnexAssociativeDistinctByKeysCollection<TKey, TValue>.TEnumerator }
constructor TEnexAssociativeDistinctByKeysCollection<TKey, TValue>.TEnumerator.Create(
  const ACollection: TEnexAssociativeDistinctByKeysCollection<TKey, TValue>);
begin
  { Initialize }
  FCollection := ACollection;
  KeepObjectAlive(FCollection);
  FEnumerator := ACollection.FCollection.GetEnumerator();
  { Create an internal set }
  FSet := THashSet<TKey>.Create(ACollection.FCollection.KeyRules);
end;
destructor TEnexAssociativeDistinctByKeysCollection<TKey, TValue>.TEnumerator.Destroy;
begin
  ReleaseObject(FCollection);
  inherited;
end;
function TEnexAssociativeDistinctByKeysCollection<TKey, TValue>.TEnumerator.GetCurrent: TPair<TKey, TValue>;
begin
  { Get from sub-enum }
  Result := FEnumerator.Current;
end;
function TEnexAssociativeDistinctByKeysCollection<TKey, TValue>.TEnumerator.MoveNext: Boolean;
begin
  while True do
  begin
    { Iterate }
    Result := FEnumerator.MoveNext;
    if not Result then
      Exit;
    { If the item is distinct, add it to set and continue }
    if not FSet.Contains(FEnumerator.Current.Key) then
    begin
      FSet.Add(FEnumerator.Current.Key);
      Exit;
    end;
  end;
end;
{ TEnexAssociativeDistinctByValuesCollection<TKey, TValue> }
constructor TEnexAssociativeDistinctByValuesCollection<TKey, TValue>.Create(
  const ACollection: TEnexAssociativeCollection<TKey, TValue>);
begin
  { Check arguments }
  if not Assigned(ACollection) then
    ExceptionHelper.Throw_ArgumentNilError('ACollection');
  { Install types }
  inherited Create(ACollection.KeyRules, ACollection.ValueRules);
  { Assign internals }
  FCollection := ACollection;
  KeepObjectAlive(FCollection);
end;
destructor TEnexAssociativeDistinctByValuesCollection<TKey, TValue>.Destroy;
begin
  { Delete the enumerable if required }
  ReleaseObject(FCollection, false);
  inherited;
end;
function TEnexAssociativeDistinctByValuesCollection<TKey, TValue>.GetEnumerator: IEnumerator<TPair<TKey, TValue>>;
begin
  { Create an enumerator }
  Result := TEnumerator.Create(Self);
end;
{ TEnexAssociativeDistinctByValuesCollection<TKey, TValue>.TEnumerator }
constructor TEnexAssociativeDistinctByValuesCollection<TKey, TValue>.TEnumerator.Create(
  const ACollection: TEnexAssociativeDistinctByValuesCollection<TKey, TValue>);
begin
  { Initialize }
  FCollection := ACollection;
  KeepObjectAlive(FCollection);
  FEnumerator := ACollection.FCollection.GetEnumerator();
  { Create an internal set }
  FSet := THashSet<TValue>.Create(ACollection.FCollection.ValueRules);
end;
destructor TEnexAssociativeDistinctByValuesCollection<TKey, TValue>.TEnumerator.Destroy;
begin
  ReleaseObject(FCollection);
  inherited;
end;
function TEnexAssociativeDistinctByValuesCollection<TKey, TValue>.TEnumerator.GetCurrent: TPair<TKey, TValue>;
begin
  { Get from sub-enum }
  Result := FEnumerator.Current;
end;
function TEnexAssociativeDistinctByValuesCollection<TKey, TValue>.TEnumerator.MoveNext: Boolean;
begin
  while True do
  begin
    { Iterate }
    Result := FEnumerator.MoveNext;
    if not Result then
      Exit;
    { If the item is distinct, add it to set and continue }
    if not FSet.Contains(FEnumerator.Current.Value) then
    begin
      FSet.Add(FEnumerator.Current.Value);
      Exit;
    end;
  end;
end;
{ TEnexSelectClassCollection<T, TOut> }
constructor TEnexSelectClassCollection<T, TOut>.Create(const ACollection: TEnexCollection<T>; const ARules: TRules<TOut>);
begin
  { Check arguments }
  if not Assigned(ACollection) then
    ExceptionHelper.Throw_ArgumentNilError('ACollection');
  { Installing the element type }
  inherited Create(ARules);
  { Assign internals }
  FCollection := ACollection;
  KeepObjectAlive(FCollection);
end;
destructor TEnexSelectClassCollection<T, TOut>.Destroy;
begin
  { Delete the enumerable if required }
  ReleaseObject(FCollection, false);
  inherited;
end;
function TEnexSelectClassCollection<T, TOut>.GetEnumerator: IEnumerator<TOut>;
begin
  { Generate an enumerator }
  Result := TEnumerator.Create(Self);
end;
{ TEnexSelectClassCollection<T, TOut>.TEnumerator }
constructor TEnexSelectClassCollection<T, TOut>.TEnumerator.Create(const ACollection: TEnexSelectClassCollection<T, TOut>);
begin
  { Initialize }
  FCollection := ACollection;
  KeepObjectAlive(FCollection);
  FEnumerator := ACollection.FCollection.GetEnumerator();
  FCurrent := default(TOut);
end;
destructor TEnexSelectClassCollection<T, TOut>.TEnumerator.Destroy;
begin
  ReleaseObject(FCollection);
  inherited;
end;
function TEnexSelectClassCollection<T, TOut>.TEnumerator.GetCurrent: TOut;
begin
  { Get current element of the "sub-enumerable" object }
  Result := FCurrent;
end;
function TEnexSelectClassCollection<T, TOut>.TEnumerator.MoveNext: Boolean;
begin
  { Iterate until given condition is met on an element }
  while True do
  begin
    Result := FEnumerator.MoveNext;
    { Terminate on sub-enum termination }
    if not Result then
      Exit;
    { Check if T is TOut. Exit if yes}
    if Assigned(FEnumerator.Current) and FEnumerator.Current.InheritsFrom(TOut) then
    begin
      FCurrent := TOut(TObject(FEnumerator.Current));
      Exit;
    end;
  end;
end;
{ TRules<T> }
class function TRules<T>.Create(const AComparer: IComparer<T>;
  const AEqualityComparer: IEqualityComparer<T>): TRules<T>;
begin
  if not Assigned(AComparer) then
    ExceptionHelper.Throw_ArgumentNilError('AComparer');
  if not Assigned(AEqualityComparer) then
    ExceptionHelper.Throw_ArgumentNilError('AEqualityComparer');
  { Initialize }
  Result.FComparer := AComparer;
  Result.FEqComparer := AEqualityComparer;
end;
class function TRules<T>.Custom(const AComparer: TCustomComparer<T>): TRules<T>;
begin
  if not Assigned(AComparer) then
    ExceptionHelper.Throw_ArgumentNilError('AComparer');
  { Init with proper stuff }
  Result.FComparer := AComparer;
  Result.FEqComparer := AComparer;
end;
class function TRules<T>.Default: TRules<T>;
begin
  { Init with proper stuff }
  Result.FComparer := TComparer<T>.Default;
  Result.FEqComparer := TEqualityComparer<T>.Default;
end;
{ TRefCountedObject }
procedure TRefCountedObject.AfterConstruction;
begin
  FInConstruction := false;
  inherited AfterConstruction();
end;
function TRefCountedObject.ExtractReference: IInterface;
var
  LRefCount: NativeInt;
begin
  { While constructing, an object has an implicit LRefCount count of 1 }
  if FInConstruction then
    LRefCount := 1
  else
    LRefCount := 0;
  {
      If the object is referenced in other places as an
      interface, get a new one, otherwise return nil
   }
  if RefCount > LRefCount then
    Result := Self
  else
    Result := nil;
end;
procedure TRefCountedObject.KeepObjectAlive(const AObject: TRefCountedObject);
var
  I, LKALen: NativeInt;
  LIntfRef: IInterface;
begin
  { Skip nil references }
  if not Assigned(AObject) then
    Exit;
  { Cannot self-ref! }
  if AObject = Self then
    ExceptionHelper.Throw_CannotSelfReferenceError();
  { Extract an optional reference, do not continue if failed }
  LIntfRef := AObject.ExtractReference();
  if not Assigned(LIntfRef) then
    Exit;
  LKALen := Length(FKeepAliveList);
  { Find a free spot }
  if LKALen > 0 then
    for I := 0 to LKALen - 1 do
      if not Assigned(FKeepAliveList[I]) then
      begin
        FKeepAliveList[I] := LIntfRef;
        Exit;
      end;
  { No free spots, extend array and insert the ref there }
  SetLength(FKeepAliveList, LKALen + 1);
  FKeepAliveList[LKALen] := LIntfRef;
end;
class function TRefCountedObject.NewInstance: TObject;
begin
  Result := inherited NewInstance();
  { Set in construction! }
  TRefCountedObject(Result).FInConstruction := true;
end;
procedure TRefCountedObject.ReleaseObject(const AObject: TRefCountedObject; const AFreeObject: Boolean);
var
  I, LKALen: NativeInt;
  LIntfRef: IInterface;
begin
  { Do nothing on nil references, since it may be calle din destructors }
  if not Assigned(AObject) then
    Exit;
  { Cannot self-ref! }
  if AObject = Self then
    ExceptionHelper.Throw_CannotSelfReferenceError();
  { Extract an optional reference, if none received, exit }
  LIntfRef := AObject.ExtractReference();
  if not Assigned(LIntfRef) then
  begin
    if AFreeObject then
      AObject.Free;
    Exit;
  end;
  LKALen := Length(FKeepAliveList);
  { Find a free spot }
  if LKALen > 0 then
    for I := 0 to LKALen - 1 do
      if FKeepAliveList[I] = LIntfRef then
      begin
        { Release the spot and kill references to the interface }
        FKeepAliveList[I] := nil;
        LIntfRef := nil;
        Exit;
      end;
end;
{ ExceptionHelper }
class procedure ExceptionHelper.Throw_ArgumentNilError(const ArgName: String);
begin
  raise EArgumentNilException.CreateFmt(SNilArgument, [ArgName]);
end;
class procedure ExceptionHelper.Throw_ArgumentOutOfRangeError(const ArgName: String);
begin
  raise EArgumentOutOfRangeException.CreateFmt(SOutOfRangeArgument, [ArgName]);
end;
class procedure ExceptionHelper.Throw_ArgumentOutOfSpaceError(const ArgName: String);
begin
  raise EArgumentOutOfSpaceException.CreateFmt(SOutOfSpaceArgument, [ArgName]);
end;
class procedure ExceptionHelper.Throw_CannotSelfReferenceError;
begin
  raise ECannotSelfReferenceException.Create(SCannotSelfReference);
end;
class procedure ExceptionHelper.Throw_CollectionChangedError;
begin
  raise ECollectionChangedException.Create(SParentCollectionChanged);
end;
class procedure ExceptionHelper.Throw_CollectionEmptyError;
begin
  raise ECollectionEmptyException.Create(SEmptyCollection);
end;
class procedure ExceptionHelper.Throw_CollectionHasMoreThanOneElement;
begin
  raise ECollectionNotOneException.Create(SCollectionHasMoreThanOneElements);
end;
class procedure ExceptionHelper.Throw_CollectionHasNoFilteredElements;
begin
  raise ECollectionFilteredEmptyException.Create(SCollectionHasNoFilteredElements);
end;
class procedure ExceptionHelper.Throw_DuplicateKeyError(const ArgName: String);
begin
  raise EDuplicateKeyException.CreateFmt(SDuplicateKey, [ArgName]);
end;
class procedure ExceptionHelper.Throw_KeyNotFoundError(const ArgName: String);
begin
  raise EKeyNotFoundException.CreateFmt(SKeyNotFound, [ArgName]);
end;
class procedure ExceptionHelper.Throw_TypeDoesNotExposeMember(const MemberName: String);
begin
  raise ENotSupportedException.CreateFmt(STypeDoesNotExposeMember, [MemberName]);
end;
class procedure ExceptionHelper.Throw_TypeNotAClassError(const TypeName: String);
begin
  raise ENotSupportedException.CreateFmt(STypeNotAClass, [TypeName]);
end;
end.