I don't think this is a bug. The behaviour is by design. Without inspection or perhaps some compile time support for understanding these types, it is hard to write a general purpose comparer for arbitrary structured types.
The default record comparer can only safely be used on types with no padding and containing only certain simple value types that can be compared using naive binary comparison. For instance, floating point types are out because their comparison operators are more complex. Think of NaNs, negative zero, etc.
I think that the only robust way to deal with this is to write your own equality comparer. Others have suggested default initialising all record instances, but this places a significant burden on consumers of such types, and runs the risk of obscure and hard to track down defects in case some code forgets to default initialise.
I would use TEqualityComparer<T>.Construct
to create suitable equality comparers. This requires the least amount of boilerplate. You supply two anonymous methods: an equals function and a hash function, and Construct
returns you a newly minted comparer.
You can wrap this up in a generic class like so:
uses
System.Generics.Defaults,
System.Generics.Collections;
{$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}
type
TPairComparer = class abstract
public
class function Construct<TKey, TValue>(
const EqualityComparison: TEqualityComparison<TPair<TKey, TValue>>;
const Hasher: THasher<TPair<TKey, TValue>>
): IEqualityComparer<TPair<TKey, TValue>>; overload; static;
class function Construct<TKey, TValue>: IEqualityComparer<TPair<TKey, TValue>>; overload; static;
end;
class function TPairComparer.Construct<TKey, TValue>(
const EqualityComparison: TEqualityComparison<TPair<TKey, TValue>>;
const Hasher: THasher<TPair<TKey, TValue>>
): IEqualityComparer<TPair<TKey, TValue>>;
begin
Result := TEqualityComparer<TPair<TKey, TValue>>.Construct(
EqualityComparison,
Hasher
);
end;
class function TPairComparer.Construct<TKey, TValue>: IEqualityComparer<TPair<TKey, TValue>>;
begin
Result := Construct<TKey, TValue>(
function(const Left, Right: TPair<TKey, TValue>): Boolean
begin
Result :=
TEqualityComparer<TKey>.Default.Equals(Left.Key, Right.Key) and
TEqualityComparer<TValue>.Default.Equals(Left.Value, Right.Value);
end,
function(const Value: TPair<TKey, TValue>): Integer
begin
Result := CombinedHash([
TEqualityComparer<TKey>.Default.GetHashCode(Value.Key),
TEqualityComparer<TValue>.Default.GetHashCode(Value.Value)
]);
end
)
end;
I've provided two overloads. If the default comparers for your two types are sufficient, then you can use the parameterless overload. Otherwise you can supply two anonymous methods bespoke to the types.
For your type, you would obtain a comparer like this:
TPairComparer.Construct<Int64, Integer>
Both of these simple types have default equality comparers that you can use. Hence the parameterless Construct
overload can be used.