Delphi 2009 generics compilation problem - delphi

I'm checking out the Delphi 2009 Trial, but run into problems with the generics stuff right away.
The following code does not compile, and I haven't the slightest idea why it's giving me E2015 for the Equals() method:
type
TPrimaryKey<T> = class(TObject)
strict private
fValue: T;
public
constructor Create(AValue: T);
function Equals(Obj: TObject): boolean; override;
function GetValue: T;
end;
constructor TPrimaryKey<T>.Create(AValue: T);
begin
inherited Create;
fValue := AValue;
end;
function TPrimaryKey<T>.Equals(Obj: TObject): boolean;
begin
Result := (Obj <> nil) and (Obj is TPrimaryKey<T>)
and (TPrimaryKey<T>(Obj).GetValue = fValue);
end;
function TPrimaryKey<T>.GetValue: T;
begin
Result := fValue;
end;
Why does the compiler think that fValue and the result of GetValue() can not be compared?

What if T is a string? What if it's a TSize record?
Without constraining T (e.g. with <T :class>), you can't be sure that the comparison will be meaningful.
If, instead, you wanted to compare two values of type T, you can use the Generics.Defaults unit and use:
TEqualityComparer<T>.Default.Equals(x, y)
to compare values x and y of type T.

You can't use operators with untyped generics. See here for a discussion.
It compiles if you change it to:
TPrimaryKey<T: class> = class(TObject)

I think the original poster is trying to create an object wrapper around simple types (Integer, double etc etc), so constraining T to Class would perhaps not work for what he wants.

The compiler has trouble in determining that both "T"'s are the same. But with a little trick you can make it work:
type
TPrimaryKey<T> = class(TObject)
public
type
TCompare<T1> = reference to function(const A1, A2: TPrimaryKey<T1>): Boolean;
private
fValue: T;
fCompare : TCompare<T>;
public
constructor Create(AValue: T; ACompare: TCompare<T>);
function Equals(Obj: TPrimaryKey<T>): Boolean; reintroduce;
function GetValue: T;
function CreateNew(const AValue: T): TPrimaryKey<T>;
end;
constructor TPrimaryKey<T>.Create(AValue: T; ACompare: TCompare<T>);
begin
inherited Create;
fValue := AValue;
fCompare := ACompare;
end;
function TPrimaryKey<T>.Equals(Obj: TPrimaryKey<T>): Boolean;
begin
Result := FCompare(self, Obj);
end;
function TPrimaryKey<T>.GetValue: T;
begin
Result := fValue;
end;
function TPrimaryKey<T>.CreateNew(const AValue: T): TPrimaryKey<T>;
begin
Result := TPrimaryKey<T>.Create(AValue, FCompare);
end;
You instantiate it with:
var
p1, p2 : TPrimaryKey<Integer>;
begin
p1 := TPrimaryKey<Integer>.Create(10,
function(const A1, A2: TPrimaryKey<Integer>): Boolean
begin
Result := (A1<>nil) and (A2<>nil) and (A1.GetValue=A2.GetValue);
end);
p2 := p1.CreateNew(10);
p1.Equals(p2);
end;

Related

How can the subclass constructor be called from the parent class?

Is there a way to invoke Create of the subclass from the parent class? Below there is this Duplicate method in which I want the constructor of the subclass to be invoked instead, so that the test at the bottom succeeds.
type
IBla<T> = interface(IInvokable)
['{34E812BF-D021-422A-A051-A492F25534C4}']
function GetIntFromIface(): Integer;
function Duplicate(): IBla<T>;
end;
TClassA<T> = class(TInterfacedObject, IBla<T>)
protected
function GetInt(): Integer; virtual;
public
function GetIntFromIface(): Integer;
function Duplicate(): IBla<T>;
end;
TClassB = class(TClassA<Integer>, IBla<Integer>)
protected
function GetInt(): Integer; override;
end;
function TClassA<T>.Duplicate: IBla<T>;
begin
Exit(TClassA<T>.Create());
end;
function TClassA<T>.GetInt: Integer;
begin
Exit(1);
end;
function TClassA<T>.GetIntFromIface: Integer;
begin
Exit(GetInt());
end;
function TClassB.GetInt: Integer;
begin
Exit(2);
end;
procedure TestRandomStuff.Test123;
var
o1, o2: IBla<Integer>;
begin
o1 := TClassB.Create();
o2 := o1.Duplicate();
Assert.AreEqual(o2.GetIntFromIface, 2);
end;
You can do this using RTTI:
uses
System.Rtti;
....
function TClassA<T>.Duplicate: IBla<T>;
var
ctx: TRttiContext;
typ: TRttiType;
mthd: TRttiMethod;
inst: TValue;
begin
typ := ctx.GetType(ClassInfo);
mthd := typ.GetMethod('Create');
inst := mthd.Invoke((typ as TRttiInstanceType).MetaclassType, []);
inst.AsObject.GetInterface(IBla<T>, Result);
end;
There is quite probably a cleaner way to invoke a constructor using RTTI (I know next to nothing about RTTI in Delphi), so you might do well to read around that topic rather than taking the above as being the canonical way to do this.
Of course, this assumes that all subclasses use a parameterless constructor defined in TObject. That might be rather limiting. I would not be surprised if you found yourself having to re-think the design in a more fundamental manner.
If none of your subclasses implement constructors then you could make it even simpler, and not use RTTI at all:
function TClassA<T>.Duplicate: IBla<T>;
begin
ClassType.Create.GetInterface(IBla<T>, Result);
end;
But be aware that this calls the constructor defined in TObject and will not call any constructor defined in a subclass.
This seems to work:
function TClassA<T>.Duplicate: IBla<T>;
begin
//Exit(TClassA<T>.Create());
Exit( ClassType.Create as TClassA<T> );
end;
The subtlety is that ClassType.Create will create (in this case) a TClassB and the original creates a TClassA< integer > which the compiler sees as different to TClassB, and hence calls TClassA< T >.GetInt rather than TClassB.GetInt.
Edit
But be aware that this calls the constructor defined in TObject and will not call any constructor defined in a subclass. (With thanks to David H)
However, here is solution that overcomes that restriction too:
interface
type
IBla<T> = interface(IInvokable)
['{34E812BF-D021-422A-A051-A492F25534C4}']
function GetIntFromIface(): Integer;
function Duplicate(): IBla<T>;
end;
TClassA<T> = class(TInterfacedObject, IBla<T>)
protected
function GetInt(): Integer; virtual;
public
constructor Create; virtual;
function GetIntFromIface(): Integer;
function Duplicate(): IBla<T>;
end;
//TClassB = class(TClassA<Integer>)
TClassB = class(TClassA<Integer>, IBla<Integer>)
protected
function GetInt(): Integer; override;
public
constructor Create; override;
function Duplicate(): IBla<Integer>;
end;
procedure Test123;
implementation
constructor TClassA<T>.Create;
begin
inherited Create;
end;
function TClassA<T>.Duplicate: IBla<T>;
begin
Exit(TClassA<T>.Create());
end;
function TClassA<T>.GetInt: Integer;
begin
Exit(1);
end;
function TClassA<T>.GetIntFromIface: Integer;
begin
Exit(GetInt());
end;
constructor TClassB.Create;
begin
inherited Create;
end;
function TClassB.Duplicate: IBla<Integer>;
begin
Result := TClassB.Create;
end;
function TClassB.GetInt: Integer;
begin
Exit(2);
end;
procedure Test123;
var
o1, o2: IBla<Integer>;
begin
o1 := TClassB.Create();
o2 := o1.Duplicate();
Assert( o2.GetIntFromIface = 2);
end;

How to free a generic TList<TMyRecord> with generic sub lists in TMyRecord

In Delphi 10 Berlin under Windows I have the following question regarding the freeing of generic lists:
I have the following record/list structure:
type
TMyRecord=record
Value1: Real;
SubList1: TList<Integer>;
SubList2: TList<Real>;
end;
TMyListOfRecords=TList<TMyRecord>;
I want to free the structure with the following code:
var
i: Integer;
AMyListOfRecords: TMyListOfRecords;
begin
//other code
//free AMyListOfRecords and all its content
for i:=0 to AMyListOfRecords.Count-1 do
begin
AMyListOfRecords[i].SubList1.Free;
AMyListOfRecords[i].SubList2.Free;
end;
AMyListOfRecords.Free;
end;
This seems to work. But I am wondering if there is a simpler or more elegant solution?
You could transform record type to class - overhead is negligible because record already contains sub-objects. Free sub-objects in this class destructor, and use
TMyListOfClasses = TObjectList<TMyClass>;
with OwnsObjects = True
In this case all you need is
AMyListOfClasses.Free;
You can define the interfaced list for the sub-items like:
type
TMyRecord=record
Value1: Real;
SubList1: IList<Integer>;
SubList2: IList<Real>;
end;
TMyListOfRecords=TList<TMyRecord>;
Where IList is kind of:
type
IList<T> = interface
function Add(const AValue: T): Integer;
function Remove(AValue: T): Integer;
end;
where you implement it like this:
TIntfList<T> = class(TInterfacedObject, IList<T>)
private
FList: TList<T>;
function Add(const AValue: T): Integer;
function Remove(AValue: T): Integer;
constructor Create;
destructor Destroy; override;
end;
{ TIntfList<T> }
function TIntfList<T>.Add(const AValue: T): Integer;
begin
Result := FList.Add(AValue);
end;
constructor TIntfList<T>.Create;
begin
FList := TList<T>.Create;
end;
destructor TIntfList<T>.Destroy;
begin
FList.Free;
inherited;
end;
function TIntfList<T>.Remove(AValue: T): Integer;
begin
Result := FList.Remove(AValue);
end;
After that you can assign fields of your record with TIntfList.Create and they will be released automatically with your records.

Use objects as keys in TObjectDictionary

When I use TObjectDictionary, where TKey is object, my application work uncorrectly.
I have two units, thats contain two classes. First unit:
unit RubTerm;
interface
type
TRubTerm = Class(TObject)
private
FRubricName: String;
FTermName: String;
public
property RubricName: String read FRubricName;
property TermName: String read FTermName;
constructor Create(ARubricName, ATermName: String);
end;
implementation
constructor TRubTerm.Create(ARubricName, ATermName: String);
begin
Self.FRubricName := ARubricName;
Self.FTermName := ATermName;
end;
end;
And second unit:
unit ClassificationMatrix;
interface
uses
System.Generics.Collections, System.Generics.Defaults, System.SysUtils, RubTerm;
type
TClassificationMatrix = class(TObject)
private
FTable: TObjectDictionary<TRubTerm, Integer>;
public
constructor Create;
procedure TClassificationMatrix.AddCount(ADocsCount: Integer; ARubName, ATermName: String);
function TClassificationMatrix.GetCount(ARubName, ATermName: String): Integer;
end;
implementation
constructor TClassificationMatrix.Create;
begin
FTable := TObjectDictionary<TRubTerm, Integer>.Create;
end;
procedure TClassificationMatrix.AddCount(ADocsCount: Integer; ARubName, ATermName: String);
var
ARubTerm: TRubTerm;
begin
ARubTerm := TRubTerm.Create(ARubName, ATermName);
FTable.Add(ARubTerm, ADocsCount);
end;
function TClassificationMatrix.GetCount(ARubName, ATermName: String): Integer;
var
ARubTerm: TRubTerm;
begin
ARubTerm := TRubTerm.Create(ARubName, ATermName);
FTable.TryGetValue(ARubTerm, Result);
end;
end;
But this fragment of code work unnormal:
procedure TestTClassificationMatrix.TestGetCount;
var
DocsCountTest: Integer;
begin
FClassificationMatrix.AddCount(10, 'R', 'T');
DocsCountTest := FClassificationMatrix.GetCount('R', 'T');
end;
// DocsCountTest = 0! Why not 10? Where is problem?
Thanks!
The fundamental issue here is that the default equality comparer for your type does not behave the way you want it to. You want equality to mean value equality, but the default comparison gives reference equality.
The very fact that you are hoping for value equality is a strong indication that you should be using a value type rather than a reference type. And that's the first change that 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've added TRubTerm.New as a helper method to make it easy to initialize new instances of the record. And for convenience, you may also find it useful to overload the equality and inequality operators, as I have done above.
Once you switch to a value type, then you would also change the dictionary to match. Use TDictionary<TRubTerm, Integer> instead of TObjectDictionary<TRubTerm, Integer>. Switching to a value type will also have the benefit of fixing all the memory leaks in your existing code. Your existing code creates objects but never destroys them.
This gets you part way home, but you still need to define an equality comparer for your dictionary. The default comparer for a record will be based on reference equality since strings, despite behaving as value types, are stored as references.
To make a suitable equality comparer 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'd implement these as static class methods of the record.
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;
Implementing EqualityComparison is easy enough:
class function TRubTerm.EqualityComparison(const Left, Right: TRubTerm): Boolean;
begin
Result := Left=Right;
end;
But the hasher requires a little more thought. You need to hash each field individually and then combine the hashes. For reference:
Quick and Simple Hash Code Combinations
What is the canonical way to write a hasher function for TEqualityComparer.Construct?
The code looks like this:
{$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 your dictionary, you need to provide an IEqualityComparison<TRubTerm>. Instantiate your dictionary like this:
Dict := TDictionary<TRubTerm,Integer>.Create(
TEqualityComparer<TRubTerm>.Construct(
TRubTerm.EqualityComparison,
TRubTerm.Hasher
)
);
A Dictionary depends on a key value. You are storing a reference to an object in the key. If you create two objects that are setup identically the have different values and hence different keys.
var
ARubTerm1: TRubTerm;
ARubTerm2: TRubTerm;
begin
ARubTerm1 := TRubTerm.Create('1', '1');
ARubTerm2 := TRubTerm.Create('1', '1');
// ARubTerm1 = ARubTerm2 is not possible here as ARubTerm1 points to a different address than ARubTerm2
end;
Instead you could uses a String as the First Type Parameter in the TObjectDictonary that is based on RubricName and TermName. With this you would then get back the same value.
It should also be noted, that above code in XE2 creates two memory leaks. Every object created must be freed. Hence this section of code also is leaking memory
function TClassificationMatrix.GetCount(ARubName, ATermName: String): Integer;
var
ARubTerm: TRubTerm;
begin
ARubTerm := TRubTerm.Create(ARubName, ATermName);
FTable.TryGetValue(ARubTerm, Result);
end;
Given all of that. If you want to use an Object as a Key you can do it with a Custom Equality Comparer. Here is your example changed to implement IEqualityComparer<T>, and fix a few memory leaks.
unit ClassificationMatrix;
interface
uses
Generics.Collections, Generics.Defaults, SysUtils, RubTerm;
type
TClassificationMatrix = class(TObject)
private
FTable: TObjectDictionary<TRubTerm, Integer>;
public
constructor Create;
procedure AddCount(ADocsCount: Integer; ARubName, ATermName: String);
function GetCount(ARubName, ATermName: String): Integer;
end;
implementation
constructor TClassificationMatrix.Create;
var
Comparer : IEqualityComparer<RubTerm.TRubTerm>;
begin
Comparer := TRubTermComparer.Create;
FTable := TObjectDictionary<TRubTerm, Integer>.Create([doOwnsKeys],TRubTermComparer.Create);
end;
procedure TClassificationMatrix.AddCount(ADocsCount: Integer; ARubName, ATermName: String);
var
ARubTerm: TRubTerm;
begin
ARubTerm := TRubTerm.Create(ARubName, ATermName);
FTable.Add(ARubTerm, ADocsCount);
end;
function TClassificationMatrix.GetCount(ARubName, ATermName: String): Integer;
var
ARubTerm: TRubTerm;
begin
ARubTerm := TRubTerm.Create(ARubName, ATermName);
try
if Not FTable.TryGetValue(ARubTerm, Result) then
result := 0;
finally
ARubTerm.Free;
end;
end;
end.
And the RubTerm.pas unit
unit RubTerm;
interface
uses Generics.Defaults;
type
TRubTerm = Class(TObject)
private
FRubricName: String;
FTermName: String;
public
property RubricName: String read FRubricName;
property TermName: String read FTermName;
constructor Create(ARubricName, ATermName: String);
function GetHashCode: Integer; override;
end;
TRubTermComparer = class(TInterfacedObject, IEqualityComparer<TRubTerm>)
public
function Equals(const Left, Right: TRubTerm): Boolean;
function GetHashCode(const Value: TRubTerm): Integer;
end;
implementation
constructor TRubTerm.Create(ARubricName, ATermName: String);
begin
Self.FRubricName := ARubricName;
Self.FTermName := ATermName;
end;
{ TRubTermComparer }
function TRubTermComparer.Equals(const Left, Right: TRubTerm): Boolean;
begin
result := (Left.RubricName = Right.RubricName) and (Left.TermName = Right.TermName);
end;
function TRubTermComparer.GetHashCode(const Value: TRubTerm): Integer;
begin
result := Value.GetHashCode;
end;
//The Hashing code was taken from David's Answer to make this a complete answer.
{$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;
function TRubTerm.GetHashCode: Integer;
begin
Result := CombinedHash([GetHashCodeString(Value.RubricName),
GetHashCodeString(Value.TermName)]);
end;
end.

Abstract error when extending classes

type
TObjA = class
a: string;
end;
type
worker<T> = interface
function step1(v: integer): T;
function step2(s: string): T;
end;
type
ImplA<T> = class(TInterfacedObject, worker<T>)
function step1(v: integer): T;
function step2(s: string): T; virtual; abstract;
end;
type
ImplB = class(ImplA<TObjA>)
function step2(s: string): TObjA;
end;
implementation
function ImplA<T>.step1(v: integer): T;
begin
result := step2(IntToStr(v));
end;
function ImplB.step2(s: string): TObjA;
var
r: TObjA;
begin
r := TObjA.Create;
r.a := 'step2 ' + s;
result := r;
end;
I am trying to build a functionality according to this structure. I know it works in java, but currently I am working in delphi 2010.
I get an abstract error when calling ImplB.step1(1)
How do I fix this?
You get the error as you do not declare function step2(s: string): TObjA; as an override.
So in
function ImplA<T>.step1(v: integer): T;
begin
result := step2(IntToStr(v));
end;
it is calling step2 from ImplA not ImplB as you are expecting it to
Your also changing the return type from a generic object to TObjA, the compiler may not like that, but I don't have a copy of Delphi that supports generics to hand to test.

How can I convert from generic to Variant in Delphi

I have a Delphi generic class that exposes a function with an argument of the generic type. Inside this function, I need to pass an instance of the generic type on to another object expecting a Variant type. Similar to this:
type
IMyInterface = interface
DoStuff(Value: Variant);
end;
TMyClass<T> = class
FMyIntf: IMyInterface
procedure DoStuff(SomeValue: T);
end;
[...]
procedure MyClass<T>.DoStuff(SomeValue: T);
begin
FMyIntf.DoStuff((*convert SomeValue to Variant here*));
end;
I tried using Rtti.TValue.From(SomeValue).AsVariant. This worked for integral types, but blew up for Booleans. I don't quite see why, since normally I'd be able to assign a Boolean value to a Variant...
Is there a better way to make this conversion? I only need it to work for simple built-in types (excluding enumerations and records)
I think there is no direct way to convert generic type to variant because variant cannot hold all the possible types. You must write your specific conversion routine. E.g.:
interface
//...
type
TDemo = class
public
class function GetAsVariant<T>(const AValue: T): Variant;
end;
//...
implementation
uses
Rtti,
TypInfo;
//...
{ TDemo}
class function TDemo.GetAsVariant<T>(const AValue: T): Variant;
var
val: TValue;
bRes: Boolean;
begin
val := TValue.From<T>(AValue);
case val.Kind of
tkInteger: Result := val.AsInteger;
tkInt64: Result := val.AsInt64;
tkEnumeration:
begin
if val.TryAsType<Boolean>(bRes) then
Result := bRes
else
Result := val.AsOrdinal;
end;
tkFloat: Result := val.AsExtended;
tkString, tkChar, tkWChar, tkLString, tkWString, tkUString:
Result := val.AsString;
tkVariant: Result := val.AsVariant
else
begin
raise Exception.Create('Unsupported type');
end;
end;
end;
Because TValue.AsVariant handles most of the type conversions internally, this function can be simplified. I will handle enumerations in case you could need them later:
class function TDemo.GetAsVariant<T>(const AValue: T): Variant;
var
val: TValue;
begin
val := TValue.From<T>(AValue);
case val.Kind of
tkEnumeration:
begin
if val.TypeInfo = TypeInfo(Boolean) then
Result := val.AsBoolean
else
Result := val.AsOrdinal;
end
else
begin
Result := val.AsVariant;
end;
end;
Possible usage:
var
vValue: Variant;
begin
vValue := TDemo.GetAsVariant<Boolean>(True);
Assert(vValue = True); //now vValue is a correct Boolean
Looks like in my Delphi version 10.2 the Boolean problem is gone and TValue.From<T>(FValue).AsVariant is enough.
Here an example with some other helpful things like comparing the generic type:
TMyValue<T> = class(TPersistent)
private
FValue: T;
procedure SetValue(const AValue: T);
function GetAsVariant: Variant; override;
public
procedure Assign(Source: TPersistent); override;
property Value: T read FValue write SetValue;
property AsVariant: Variant read GetAsVariant;
end;
function TMyValue<T>.GetAsVariant: Variant;
begin
Result:= TValue.From<T>(FValue).AsVariant;
end;
procedure TMyValue<T>.SetValue(const AValue: T);
begin
if TEqualityComparer<T>.Default.Equals(AValue, FValue) then Exit;
FValue:= AValue;
//do something
end;
procedure TMyValue<T>.Assign(Source: TPersistent);
begin
if Source is TMyValue<T> then Value:= (Source as TMyValue<T>).Value
else inherited;
end;
Another way (tested XE10)
Var
old : variant;
val : TValue;
Begin
val := TValue.FromVariant(old);
End;

Resources