- edited description
Nullable<T> does not support injecting a custom IEqualityComparer<T>
I would suggest changing the visibility of class var fComparer: IEqualityComparer<T>;
of Nullable<T>
from private
to public
(and perhaps rename it to Comparer
).
Nullable<T>
already seems well prepared for it:
class function Nullable<T>.EqualsComparer(const left, right: T): Boolean;
begin
if not Assigned(fComparer) then
fComparer := TEqualityComparer<T>.Default;
Result := fComparer.Equals(left, right);
end;
Reason: I would like to provide my own comparer as this would allow me to continue using the Equals(..)
method (or operator overload) of TNullable<T>
for types of T
that cannot be handled by the logic already implemented in Base/Spring.pas
.
Example:
program Spring4D_Issue299;
{$APPTYPE CONSOLE}
uses System.Generics.Defaults, Spring;
type
TStruct = record
values: TBytes;
class operator Equal(a, b: TStruct): Boolean;
class function EqualityComparer(): IEqualityComparer<TStruct>; static;
end;
class operator TStruct.Equal(a, b: TStruct): Boolean;
begin
// left out for brevity
end;
class function TStruct.EqualityComparer: IEqualityComparer<TStruct>;
begin
// left out for brevity
end;
procedure p();
var
a, b: TStruct;
x, y: Nullable<TStruct>;
begin
SetLength(a.values, 1);
a.values[0] := 99;
SetLength(b.values, 1);
b.values[0] := 99;
Assert(a = b);
x := a;
y := b;
// If I could do this:
// Nullable<TStruct>.Comparer := TStruct.EqualityComparer()
// Then this would not fail:
Assert(x = y);
end;
begin
p();
end.
Many thanks for your time.
Comments (10)
-
reporter -
reporter - changed title to Nullable<T> does not support injecting a custom IEqualityComparer<T>
-
repo owner Wouldn't picking an equalitycomparer that knows how to handle the equals operator be enough? Or even better try to handle that directly in EqualsInternal for tkRecord
Sure, that leaves out all records that don't have equals operator but one could argue that then also a nullable of such type should not be comparable.
-
reporter I am probably missing something - I don't see where I could pass my equality comparer. For I both example variables
x, y: Nullable<TStruct>
, I must not usex.Equals(y)
orx = y
and will instead have to go with a rather lengthyif (x.HasValue and y.HasValue) and (x.Value = y.Value) then …
PS: I have no idea how
EqualsInternal
could be extended to detect whether a record has an operator overload for equality. Is that even possible?(I probably completely misunderstood)
-
repo owner Yes, RTTI contains information of operator overloads, they are just static methods on the type so they can be put into a function(const left, right: T): Boolean pointer and be called. No overhead. The Nullable type can read them in its class constructor - I just need to check since which version that is available since I know that XE did not have RTTI for class operators.
This is how the code looks like to get an equals operator if available. I would use that to detect if there is any and use that in the
EqualsInternal
call in case of tkRecord.function GetEqualsOperator(const typeInfo: PTypeInfo): Pointer; var parameters: TArray<TRttiParameter>; method: TRttiMethod; begin for method in TType.GetType(typeInfo).GetMethods('&op_Equality') do begin if method.MethodKind <> mkOperatorOverload then Continue; parameters := method.GetParameters; if (Length(parameters) = 2) and (parameters[0].ParamType.Handle = typeInfo) and (parameters[1].ParamType.Handle = typeInfo) then Exit(method.CodeAddress); end; Result := nil; end;
Need to do some testing on various Delphi versions to figure out the best solution - but I am currently in the middle of some rather big refactoring so will take a few days - I will probably add it to 1.2.2
-
reporter This sounds extremely exciting, I never knew it was possible. Just determining it once in the class constructor also sounds pretty clever.
There is one minor thing: I think you are not correct about
CodeAddress
pointing to afunction(const left, right: T): Boolean
, I suppose the parameters are notconst
which is a shame because it would have been matching withTEqualityComparison<T>
. You can checkparameters[0]
and[1]
for theirFlags
which are empty and do not includeTParamFlag.pfConst
. For value-types like records, this is extremely important. -
repo owner You can declare the parameters as you want in some operators, however I usually make them const.
-
reporter That is entirely new to me. I was sticking to the official documentation which clearly did not have the parameters
const
but as it turns out, you really can make themconst
. Neat.I was just pointing out that I would strongly recommend checking for the
const
flags as calling the&op_Equality
method from my code above by passing the records by reference would be disastrous 😎 -
repo owner It would not because the parameter passing is exactly the same. See https://www.guidogybels.eu/asmtable3.html
Only difference which is not contained in that table because it is rather new is when you add the [ref] attribute to a const parameter which forces to pass as pointer even if it would fit into a register.
-
repo owner - changed status to resolved
Nullable<T> considers equal operator overload on record types - parameters need to be marked as const (fixes
#299)→ <<cset d224d3ac460b>>
- Log in to comment