MarkF
MarkF

Reputation: 1646

How to create and use a case insensitive IList<string> in Spring4d

I'm trying the following code to create a case insensitive IList:

procedure TForm1.ListButtonClick(Sender: TObject);
var
  MyList: IList<string>;
begin
  MyList := TCollections.CreateList<string>(TStringComparer.OrdinalIgnoreCase());
  MyList.AddRange(['AAA', 'BBB', 'CCC']);
  Memo1.Lines.Add(MyList.IndexOf('aaa').ToString);
end;

However the IndexOf call always returns -1. Should this work? Any suggestions appreciated.

Update: It looks like the comparer is used for sorting, but not for IndexOf. A separate "EqualityComparer" is used for IndexOf, so the question becomes how to change it?

Update2: I just wanted to add to Johan's answer that the list can then be created like so:

MyCIList := TCaseInsensitiveList<string>.Create(
  TStringComparer.OrdinalIgnoreCase(),
  TStringComparer.OrdinalIgnoreCase());

Upvotes: 3

Views: 897

Answers (2)

MarkF
MarkF

Reputation: 1646

This issue has been resolved in Spring4D hotfix 1.2.1. With the change the code below works as expected.

procedure TForm1.ListButtonClick(Sender: TObject);
var
  MyList: IList<string>;
begin
  MyList := TCollections.CreateList<string>(TStringComparer.OrdinalIgnoreCase());
  MyList.AddRange(['AAA', 'BBB', 'CCC']);
  Memo1.Lines.Add(MyList.IndexOf('aaa').ToString);  // Correctly returns 0.
end;

Upvotes: 2

Johan
Johan

Reputation: 76693

The code of IndexOf looks like this:

function TList<T>.IndexOf(const item: T; index, count: Integer): Integer;
begin
  Result := TArray.IndexOf<T>(fItems, item, index, count, EqualityComparer);
end;

The EqualityComparer is a property that calls GetEqualityComparer which looks like:

protected
  class function GetEqualityComparer: IEqualityComparer<T>; static;

This static means the method cannot be overriden in a child class.

You need to edit the source of Spring and make the following change:

Spring.Collections.Base

103:   TEnumerableBase<T> = class abstract(TEnumerableBase, IEnumerable<T>)
106:   protected
108:   class var fEqualityComparer: IEqualityComparer<T>;  
//     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//Move this line from private to the protected section.
.......

                                                                ^^^^^^^^

Now you can create your own TCaseInsensitiveList like so:

type
  TCaseInsensitiveList<T> = class(Spring.Collections.TList<T>)
    constructor Create(const Comparer: IComparer<T>; 
                       const EqualityComparer: IEqualityComparer<T>);
  end;
  .....
constructor TCaseInsensitiveList<T>.Create(
             const Comparer: IComparer<T>; 
             const EqualityComparer: IEqualityComparer<T>);
begin
  inherited Create(Comparer);
  Self.fEqualityComparer:= EqualityComparer;
end;

Or alternatively you can declare the base GetEqualityComparer class function to be virtual and override that in a child class.

There is a cost to making the GetEqualityComparer virtual, which is why I choose to make the underlying class variable protected.

Upvotes: 1

Related Questions