David did a great job writing a textual description of how the default mappings work, but for some of you it may be easier to follow when you see how the base code is structured (and decide if the defaults are applied).
I will only talk about Compare_ 's style of comparison. The Equals_ style works the same way.
What happens is that _LookupVtableInfo selects the IComparer interface to compare Compare_ styles (and IEqualityComparer style for Equals_ ).
Under these interfaces are not ordinary interfaces, but interface shells around the global functions of this form for the Compare_ style:
function Compare_t<T>(Inst: Pointer; const Left, Right: T): Integer;
and global procedures of this form for the Equals_ style:
function Equals_t<T>(Inst: Pointer; const Left, Right: T): Integer; function GetHashCode_t<T>(Inst: Pointer; const Left, Right: T): Integer;
The result of Compare_ style Compare_ is simple, but slightly different from -1, 0, +1, which some people might expect:
< 0 for Left < Right = 0 for Left = Right > 0 for Left > Right
In most cases, the implementation is very simple:
I grouped the Compare_ style functions Compare_ how they do it.
- Common types (including enumerations and Int64).
- Floating point types (Real) (including Comp and Currency).
- Short lines (from Turbo Pascal / Delphi 1 day).
- Wide lines (OLE styles).
- Methods
- Pointers (including classes, interfaces, references to classes and procedures).
(Regular types outside the range of 1, 2, 4, 8 bytes and real types outside the range of 4, 8, 10 bytes cause an error because they are illegal).
The first group simply subtracts Left from Right: integers with signature / unsigned 1 or 2 bytes long
function Compare_I1(Inst: Pointer; const Left, Right: Shortint): Integer; function Compare_I2(Inst: Pointer; const Left, Right: Smallint): Integer; function Compare_U1(Inst: Pointer; const Left, Right: Byte): Integer; function Compare_U2(Inst: Pointer; const Left, Right: Word): Integer; Result := Left - Right;
The second group makes a comparison:
function Compare_I4(Inst: Pointer; const Left, Right: Integer): Integer; function Compare_I8(Inst: Pointer; const Left, Right: Int64): Integer; function Compare_U4(Inst: Pointer; const Left, Right: LongWord): Integer; function Compare_U8(Inst: Pointer; const Left, Right: UInt64): Integer; function Compare_R4(Inst: Pointer; const Left, Right: Single): Integer; function Compare_R8(Inst: Pointer; const Left, Right: Double): Integer; function Compare_R10(Inst: Pointer; const Left, Right: Extended): Integer; function Compare_RI8(Inst: Pointer; const Left, Right: Comp): Integer; function Compare_RC8(Inst: Pointer; const Left, Right: Currency): Integer; function Compare_WString(Inst: PSimpleInstance; const Left, Right: WideString): Integer; function Compare_Pointer(Inst: PSimpleInstance; Left, Right: NativeUInt): Integer; type {$IFNDEF NEXTGEN} TPS1 = string[1]; TPS2 = string[2]; TPS3 = string[3]; {$ELSE NEXTGEN} OpenString = type string; TPS1 = string; TPS2 = string; TPS3 = string; {$ENDIF !NEXTGEN} function Compare_PS1(Inst: PSimpleInstance; const Left, Right: TPS1): Integer; function Compare_PS2(Inst: PSimpleInstance; const Left, Right: TPS2): Integer; function Compare_PS3(Inst: PSimpleInstance; const Left, Right: TPS3): Integer;
Now we get to the interesting bits: not so direct results.
Strings use CompareStr . If you want something else, you can use TOrdinalIStringComparer
function Compare_LString(Inst: PSimpleInstance; const Left, Right: AnsiString): Integer; function Compare_UString(Inst: PSimpleInstance; const Left, Right: UnicodeString): Integer; Result := CompareStr(Left, Right);
BinaryCompare used for:
- binary data, including unknowns, Char / WChar, Set, Array, Record. An exception is if binary data is 1, 2 or 4 bytes in x86 and x64 and 8 bytes in x64 in size, they will be compared as integers.
- dynamic frames (be careful when they are multidimensional!).
- as a last resort (see below)
For records that can be compared, it makes sense to overload the operator and use them for comparison.
Binary data of 1, 2, 4 or 8 bytes is an exception, which will give strange results on machines with a small limb (Intel x86 and x64 and a bi-directional lever in little-endian mode):
function Comparer_Selector_Binary(info: PTypeInfo; size: Integer): Pointer; begin case size of
The rest is pure binary:
function Compare_Binary(Inst: PSimpleInstance; const Left, Right): Integer; begin Result := BinaryCompare(@Left, @Right, Inst^.Size); end; function Compare_DynArray(Inst: PSimpleInstance; Left, Right: Pointer): NativeInt; var len, lenDiff: NativeInt; begin len := DynLen(Left); lenDiff := len - DynLen(Right); if lenDiff < 0 then Inc(len, lenDiff); Result := BinaryCompare(Left, Right, Inst^.Size * len); if Result = 0 then Result := lenDiff; end;
As usual, the Variants are in a separate league. First attempt at VarCompareValue . If this fails, then a Compare_UString check is Compare_UString . If this fails, BinaryCompare checked. If it fails: hard luck.
function Compare_Variant(Inst: PSimpleInstance; Left, Right: Pointer): Integer; var l, r: Variant; lAsString, rAsString: string; begin Result := 0; // Avoid warning. l := PVariant(Left)^; r := PVariant(Right)^; try case VarCompareValue(l, r) of vrEqual: Exit(0); vrLessThan: Exit(-1); vrGreaterThan: Exit(1); vrNotEqual: begin if VarIsEmpty(L) or VarIsNull(L) then Exit(1) else Exit(-1); end; end; except // if comparison failed with exception, compare as string. try lAsString := PVariant(Left)^; rAsString := PVariant(Right)^; Result := Compare_UString(nil, lAsString, rAsString); except // if comparison fails again, compare bytes. Result := BinaryCompare(Left, Right, SizeOf(Variant)); end; end; end;