David 在文本描述默认比较器如何工作方面做得很好,但是对于你们中的一些人来说,当您看到底层代码的结构(并决定默认比较器是否适用)时,可能会更容易理解。
我将只Compare_
介绍比较的风格。这种Equals_
风格以类似的方式工作。
发生的情况是_LookupVtableInfo
选择一个用于样式比较的IComparer
界面(和一个用于样式)。Compare_
IEqualityComparer
Equals_
在这些接口下面不是普通接口,而是围绕这种形式的全局函数的接口包装器Compare_
:
function Compare_t<T>(Inst: Pointer; const Left, Right: T): Integer;
和这种形式的全局程序Equals_
样式:
function Equals_t<T>(Inst: Pointer; const Left, Right: T): Integer;
function GetHashCode_t<T>(Inst: Pointer; const Left, Right: T): Integer;
样式函数的结果Compare_
很简单,但与某些人可能期望的 -1、0、+1 略有不同:
< 0 for Left < Right
= 0 for Left = Right
> 0 for Left > Right
对于大多数情况,实现非常简单:
我按照Compare_
样式函数的执行方式对样式函数进行了分组。
- 序数类型(包括枚举数和 Int64)。
- 浮点(Real)类型(包括 Comp 和 Currency)。
- 短字符串(来自 Turbo Pascal/Delphi 1 天)。
- 宽字符串(OLE 样式的)。
- 方法。
- 指针(包括类、接口、类引用和过程)。
(超出 1、2、4、8 字节范围的序数类型和超出 4、8、10 字节范围的实数类型会引发错误,因为它们是非法的)。
第一组只是从右减去左:1 或 2 字节长度的有符号/无符号整数
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;
第二组做一个比较:
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;
// OpenString allows for any String[n], see http://my.safaribooksonline.com/book/programming/borland-delphi/1565926595/5dot-language-reference/ch05-openstring
function Compare_PSn(Inst: PSimpleInstance; const Left, Right: OpenString): Integer;
if Left < Right then
Result := -1
else if Left > Right then
Result := 1
else
Result := 0;
function Compare_Method(Inst: PSimpleInstance; const Left, Right: TMethodPointer): Integer;
var
LMethod, RMethod: TMethod;
begin
LMethod := TMethod(Left);
RMethod := TMethod(Right);
if LMethod < RMethod then
Result := -1
else if LMethod > RMethod then
Result := 1
else
Result := 0;
end;
现在我们来看看有趣的部分:不那么直截了当的结果。
字符串使用CompareStr
. 如果你想要不同的东西,你可以使用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
是用来:
- 二进制数据包括未知、Char/WChar、Set、Array、Record。如果二进制数据在 x86 和 x64 中为 1、2 或 4 字节大小以及在 x64 中为 8 字节,则将其作为整数进行比较。
- 动态数组(多维时要小心!)。
- 变体作为最后的手段(见下文)
对于可以比较的记录,执行运算符重载并让比较器使用这些运算符是有意义的。
1、2、4 或 8 字节的二进制数据是一个例外,它会在 little-endian 机器(Intel x86 和 x64,以及 little-endian 模式下的 bi-endian Arm)上给出奇怪的结果:
function Comparer_Selector_Binary(info: PTypeInfo; size: Integer): Pointer;
begin
case size of
// NOTE: Little-endianness may cause counterintuitive results,
// but the results will at least be consistent.
1: Result := @Comparer_Instance_U1;
2: Result := @Comparer_Instance_U2;
4: Result := @Comparer_Instance_U4;
{$IFDEF CPUX64}
// 64-bit will pass const args in registers
8: Result := @Comparer_Instance_U8;
{$ENDIF}
else
Result := MakeInstance(@Comparer_Vtable_Binary, size);
end;
end;
其余的是纯二进制:
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;
像往常一样,Variants
他们都在自己的联盟中。首先VarCompareValue
是尝试。如果失败,则Compare_UString
尝试。如果这也失败了,BinaryCompare
则尝试。如果失败:运气不好。
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;