The main problem here is that the default mapping for your type does not behave the way you want it. You want equality to mean equality of values, but comparison by default gives referential equality.
The fact that you are hoping for equal value is convincing evidence that you should use a value type, not a reference type. And this is the first change I would suggest.
type TRubTerm = record RubricName: string; TermName: string; class function New(const RubricName, TermName: string): TRubTerm; static; class operator Equal(const A, B: TRubTerm): Boolean; class operator NotEqual(const A, B: TRubTerm): Boolean; end; class function TRubTerm.New(const RubricName, TermName: string): TRubTerm; begin Result.RubricName := RubricName; Result.TermName := TermName; end; class operator TRubTerm.Equal(const A, B: TRubTerm): Boolean; begin Result := (A.RubricName=B.RubricName) and (A.TermName=B.TermName); end; class operator TRubTerm.NotEqual(const A, B: TRubTerm): Boolean; begin Result := not (A=B); end;
I added TRubTerm.New as a helper method to simplify the initialization of new record instances. And for convenience, you can also use equality and inequality to overload operators, as I said above.
Once you switch to value type, you will also change the dictionary to match. Use TDictionary<TRubTerm, Integer> instead of TObjectDictionary<TRubTerm, Integer> . Switching to a value type will also have the advantage of fixing all memory leaks in existing code. Your existing code creates objects, but does not destroy them.
This gives you part of the way home, but you still need to determine the comparative equality factor for your vocabulary. The default comparison tool for writing will be based on reference equality, because strings, although they behave like value types, are stored as references.
To make a suitable TRubTerm , you need to implement the following comparison functions, where T is replaced by TRubTerm :
TEqualityComparison<T> = reference to function(const Left, Right: T): Boolean; THasher<T> = reference to function(const Value: T): Integer;
I would use them as static methods of a recording class.
type TRubTerm = record RubricName: string; TermName: string; class function New(const RubricName, TermName: string): TRubTerm; static; class function EqualityComparison(const Left, Right: TRubTerm): Boolean; static; class function Hasher(const Value: TRubTerm): Integer; static; class operator Equal(const A, B: TRubTerm): Boolean; class operator NotEqual(const A, B: TRubTerm): Boolean; end;
The implementation of EqualityComparison quite simple:
class function TRubTerm.EqualityComparison(const Left, Right: TRubTerm): Boolean; begin Result := Left=Right; end;
But a hasher requires a little more thought. You need to hash each field separately and then combine the hashes. For reference:
The code is as follows:
{$IFOPT Q+} {$DEFINE OverflowChecksEnabled} {$Q-} {$ENDIF} function CombinedHash(const Values: array of Integer): Integer; var Value: Integer; begin Result := 17; for Value in Values do begin Result := Result*37 + Value; end; end; {$IFDEF OverflowChecksEnabled} {$Q+} {$ENDIF} function GetHashCodeString(const Value: string): Integer; begin Result := BobJenkinsHash(PChar(Value)^, SizeOf(Char) * Length(Value), 0); end; class function TRubTerm.Hasher(const Value: TRubTerm): Integer; begin Result := CombinedHash([GetHashCodeString(Value.RubricName), GetHashCodeString(Value.TermName)]); end;
Finally, when you instantiate the dictionary, you need to provide IEqualityComparison<TRubTerm> . Create your dictionary as follows:
Dict := TDictionary<TRubTerm,Integer>.Create( TEqualityComparer<TRubTerm>.Construct( TRubTerm.EqualityComparison, TRubTerm.Hasher ) );