Spring4d: Spring.Collections.IEnumerator and System.IEnumerator - delphi

I have a problem that should be trivial but to which I can't find any elegant answer.
I have an instance of a IList<string> and I want to get a comma-separated string of all its distinct (case-insensitive) values.
I thought I'd just use the string.Join helper for that since it has a nice overload that accepts an IEnumerator<string> as parameter. Unfortunately, I see to have hit a snag: spring4d redefines IEnumerator<T> and, of course, use its own type everywhere.
The result is that the following code does not compile:
var
distinct: system.IEnumerator<string>;
begin
result := inherited GetToken;
if assigned(result) then
begin
if not Modules.Contains(STR_DID_SESSION_MODULE) then
Modules.Add(STR_DID_SESSION_MODULE);
distinct := TDistinctIterator<string>.Create(Modules, TIStringComparer.Ordinal);
result.CustomClaims.Items[STR_CLAIM_CUSTOM_MODULES] := string.Join(',', distinct);
end;
end;
The assignment to distinct fails with E2010 Incompatible types: 'System.IEnumerator<System.string>' and 'Spring.Collections.Extensions.TDistinctIterator<System.string>'
Alternatively, if I remove the namespace from distinct, it's the call to string.Join that fails.
Any idea how I should be doing that ? Short of manually walking through the iteration and performing the join manually?

Write it yourself (FWIW I would prefer opposite parameter order but I kept it like that since the signature of TStringHelper.Join):
function StringJoin(const separator: string; const values: Spring.Collections.IEnumerable<string>): string; overload;
var
e: Spring.Collections.IEnumerator<string>;
begin
e := values.GetEnumerator;
if not e.MoveNext then
Exit('');
Result := e.Current;
while e.MoveNext do
Result := Result + separator + e.Current;
end;
Also you can write the code way shorter (no need to manually create the iterator from Spring.Collections.Extensions):
StringJoin(',', TEnumerable.Distinct<string>(Modules, TStringComparer.OrdinalIgnoreCase))
Now if we had interface helpers we could easily write a helper for IEnumerable<string> and add ToDelimitedString or something like that.

Related

E2555 Cannot capture symbol 'Self'

When I run the following code I get E2555 Cannot capture symbol 'Self'.
type
TLookupTable = record
FData: TArray<TOtherRec>;
procedure ReverseLeftToRight;
end;
procedure TLookupTable.ReverseLeftToRight;
begin
Parallel.For(0, Length(FData)-1).NoWait.Execute(procedure(i: integer) begin
FData[i]:= FData[i].ReverseLeftRight;
end); {for i}
end;
How do I fix this?
The problem is that var parameters (including hidden var parameters to Self) do not get captured. However, we do not want to copy the record, that would be useless, because then our method would not work.
The trick is to make the hidden self parameter explicit.
If it's a class, then it's easy (var S:= Self), if not you'll have to declare a pointer to your record.
procedure TLookupTable.ReverseLeftToRight;
type
PLookupTable = ^TLookupTable;
var
S: PLookupTable;
begin
S:= #Self;
Parallel.For(0, Length(FData)-1).NoWait.Execute(procedure(i: integer) begin
S.FData[i]:= S.FData[i].ReverseLeftRight;
end); {for i}
end;
Now the compiler no longer complains.
(Note that I'm using the implicit syntax for S^.xyz).
Delphi Rio
Using a inline var declaration as shown below, does not work.
//S: PLookupTable;
begin
var S:= #Self; //inline declaration
Parallel.For(0, Length(FData)-1).NoWait.Execute(procedure(i: integer) begin
S.FData[i]:= S.FData[i].ReverseLeftRight;
end); {for i}
This generates: E2018 Record, object or class type required.
I guess the inline #Self gets resolved to a generic pointer, which is a shame, because there is enough info to infer the correct type for the inline variable.
Asynchronous issues
If you're executing the code using a Async (.NoWait) thread/task, then it might be better to put FData in the local variable. FData, being a dynamic array, is already a pointer (so no copying will take place, just a ref count). And dynamic arrays do not have Copy-on-Write semantics, so the original will get updated.
As is, the Self record might go out of scope whilst the code is running, because the pointer operation S:= #Self does not cause the reference count on FData to increase). This might cause an access violation (or worse).
Taking a reference to FData causes its refcount to go up, meaning it cannot go out of scope prematurely.

Rtti invoke a class method says invalid type cast

I have made a unit that's very generic and settings objects in this are all TObjects i don't want to make uses of any units that's why i'm doing this. So my approach is to use RTTI to call everything. But now i face a problem where i can call all the functions and give arguments and everything but when the method is a class procedure/function i can't call it and it says invalid type cast.
I checked on embarcadero's website it says that when we call rtti.invoke on a classmethod we have to set the first argument in Args as a class reference. I tried that but it doesn't work. Take a look at my code:
function TSomething.ExecMethodAndRet(MethodName: string;
Args: array of TValue): TObjectList<TObject>;
var
R : TRttiContext;
T : TRttiType;
M : TRttiMethod;
lArgs : array of TValue;
i : integer;
begin
T := R.GetType(MainObj.ClassInfo);
for M in t.GetMethods do
if (m.Parent = t) and (UpperCase(m.Name) = UpperCase(MethodName))then
begin
if (m.IsClassMethod) then
begin
for I := 0 to Length(Args) do
lArgs := [args[i]];
lArgs := [MainObj] + lArgs;
result := M.Invoke(MainObj, Args).AsType<TObjectList<TObject>>; <- this will say invalid type cast
end
else
result := M.Invoke(MainObj, Args).AsType<TObjectList<TObject>>; <- this one works when it's not a classMethod that's why i made a condition
end;
end;
I don't know what i'm doing wrong. Maybe it's not possible to do it without knowing the type of the object. My Main Obj is a TObject that is of the required type and i can call the methods of it. But That class procedure is really giving me a hard time.
Someone knows how i could achieve this?
Instead of the instance use
M.Invoke(MainObj.ClassType,

Are Delphi record constructors really needed?

SITUATION
I am studying "More Coding in Delphi" by Nick Hodges, and he is using a TFraction record to explain operator overloading. I have written by myself this record:
type
TFraction = record
strict private
aNumerator: integer;
aDenominator: integer;
function GCD(a, b: integer): integer;
public
constructor Create(aNumerator: integer; aDenominator: integer);
procedure Reduce;
class operator Add(fraction1, fraction2: TFraction): TFraction;
class operator Subtract(fraction1, fraction2: TFraction): TFraction;
//... implicit, explicit, multiply...
property Numerator: integer read aNumerator;
property Denominator: integer read aDenominator;
end;
Of course, I had to create a constructor because in Q (rationals) I must have a denominator that is not equal to zero.
constructor TFraction.Create(aNumerator, aDenominator: integer);
begin
if (aDenominator = 0) then
begin
raise Exception.Create('Denominator cannot be zero in rationals!');
end;
if ( (aNumerator < 0) or (aDenominator < 0) ) then
begin
Self.aNumerator := -aNumerator;
Self.aDenominator := -aDenominator;
end
else
begin
Self.aNumerator := aNumerator;
Self.aDenominator := aDenominator;
end;
end;
PROBLEM
Since the operator overloads return a TFraction, I am going to define an operation like this:
class operator TFraction.Add(fraction1, fraction2: TFraction): TFraction;
var
tmp: TFraction;
begin
//simple algorithm of the sum
tmp := TFraction.Create(fraction1.Numerator*fraction2.Denominator+fraction1.Denominator*fraction2.Numerator, fraction1.Denominator*fraction2.Denominator);
tmp.Reduce;
//return the result
Result := tmp;
end;
As you can see here, I am creating a tmp that is returned from the function.
When I read Marco Cantu's book, he used another approach:
class operator TFraction.Add(fraction1, fraction2: TFraction): TFraction;
begin
Result.aNumerator := (fraction1.Numerator*fraction2.Denominator+fraction1.Denominator*fraction2.Numerator);
Result.aDenominator := fraction1.Denominator*fraction2.Denominator;
end;
I have made some tests, and I see that both give me the correct result, but there is something that I cannot understand. In the first approach, I am declaring tmp and then I call the constructor so I can return a TFraction. In the second approach, I am instead not creating anything because records have an automatic constructor. The documentation, in fact, says that:
Records are constructed automatically, using a default no-argument
constructor, but classes must be explicitly constructed. Because
records have a default no-argument constructor, any user-defined
record constructor must have one or more parameters.
Here I have a user-defined record constructor. So:
Is the constructor call on tmp of the first approach not needed? If I want to call Reduce (which is a procedure), I need to create a variable. Is the Result just returning a copy of tmp without creating anything?
In the second approach, are Result.aNumerator and Result.aDenominator the parameters of the automatic created constructor?
A record constructor isn't anything magical. It's just an instance method like any other. You write:
tmp := TFraction.Create(...);
But you may equally well write it like this:
tmp.Create(...);
I personally find neither to be especially useful because I am used to constructor calling semantics for classes which allocate and default initialise memory, and then call the constructor method.
And especially the second variant grates with me because that looks like the classic mistake that novice Delphi programmers make when starting out and trying to create an instance of a class. That code would be no good if TFraction were a class, but for a record it is fine.
Were it me I would get rid of the record constructor and instead use a static class function that returned a newly minted instance of your record type. My convention is to name such things New. But these are matters of personal preference.
If you did that it would be declared like this:
class function New(aNumerator, aDenominator: Integer): TFraction; static;
It would be implemented like this:
class function TFraction.New(aNumerator, aDenominator: Integer): TFraction;
begin
Result.aNumerator := ...;
Result.aDenominator := ...;
end;
You would then call it like this:
frac := TFraction.New(num, denom);
But as I said, that's a matter of preference. If you like record constructors, feel free to stick with them.
You ask whether or not you can skip the constructor. In terms of allocation of the record, yes you can skip it. In terms of running the code in the constructor, only you can determine that. Do you want that code to execute or not?
If you wish that code to be executed, but don't want to use a temporary variable, then you can write the code like this:
class operator TFraction.Add(fraction1, fraction2: TFraction): TFraction;
begin
Result.Create(
fraction1.Numerator*fraction2.Denominator + fraction1.Denominator*fraction2.Numerator,
fraction1.Denominator*fraction2.Denominator
);
Result.Reduce;
end;
Or if you preferred a static class function it would be:
class operator TFraction.Add(fraction1, fraction2: TFraction): TFraction;
begin
Result := TFraction.New(
fraction1.Numerator*fraction2.Denominator + fraction1.Denominator*fraction2.Numerator,
fraction1.Denominator*fraction2.Denominator
);
Result.Reduce;
end;

Creating property to store two values

Using Delphi 10 I have two values work_start and work_finish of type TTime that I need to read and write from database table so I though to create a property for each one like that
private
fWorkStart: TTime;
function GetWS: TTime;
procedure SetWS(const Value: TTime);
Public
property WorkStart: TTime read GetWS write SetWS;
....
procedure MyClass.SetWS(const Value: TTime);
begin
fWorkStart := value;
mydataset.Edit;
mydataset.FieldByName('work_start').AsDateTime := fWorkStart;
mydataset.Post;
end;
function MyClass.GetWS: TTime;
begin
if mydataset.FieldByName('work_start').IsNull then
fWorkStart := encodetime(6,0,0,0)
else
fWorkStart := mydataset.FieldByName('work_start').AsDateTime;
result := fWorkStart;
end;
WorkFinish property is the same. So is there a way to create one property for both times or my code is fine ?
Craig's answer demonstrates record properties, which means you have a single property that gets set as a unit; you can't set the start and finish times independently. Dawood's answer demonstrates an array property, which allows independent accesses, but imposes cumbersome bracket notation on the consumer. Kobik's comment improves the semantics, but we can do even better using index specifiers.
First, define an enum to represent the two kinds of times:
type
TWorkTime = (wtStart, wtFinish);
Use those values in your property declarations, and provide an extra parameter to your property accessors to represent the index:
private
FWorkTime: :array[TWorkTime] of TTime;
function GetWT(Index: TWorkTime): TTime;
procedure SetWT(Index: TWorkTime; const Value: TTime);
public
property WorkStart: TTime index wsStart read GetWT write SetWT;
property WorkFinish: TTime index wsFinish read GetWT write SetWT;
To reduce the bloat Craig warns about in your accessors, you can define another array with the corresponding fields names, which lets you avoid duplicating code for your different fields:
const
FieldNames: array[TWorkTime] of string = (
'work_start',
'work_finish'
);
function MyClass.GetWT(Index: TWorkTime): TTime;
begin
if mydataset.FieldByName(FieldName[Index]).IsNull then
FWorkTime[Index] := EncodeTime(6, 0, 0, 0)
else
FWorkTime[Index] := mydataset.FieldByName(FieldNames[Index]).AsDateTime;
Result := FWorkTime[Index];
end;
It is possible:
//Define a record to hold both
type
TTimeRange = record
StartTime: TTime;
EndTime: TTime;
end;
//And have your property use the record
property WorkHours: TTimeRange read GetWorkHours write SetWorkHours;
However, this would force clients of your class to interact using the record structure. Basically the complications you'd encounter outweigh the small benefit you'd gain.
So I don't recommend it.
(Although it's worth remembering the technique because in other scenarios it may prove more useful.)
As for your code:
Handling of properties is fine. Although in the code you've presented fWorkStart is redundant.
I'd caution against Edit and Post within your property writer. Apart from the fact that updating 1 field at a time in the Db would be highly inefficient, your method has unexpected side-effects. (And can you always assume edit is the right choice and not insert?)
In your property reader, assuming NULL == 6:00 is not a good idea. NULL has very specific meaning that the value is unknown/unassigned. Defaulting it in the wrong place leads to being unable to tell the difference between 6:00 and NULL. (I'm not saying never default a null; just understand the implications.)
yes you can use indexed properties
property WorkTime[IsStart: Boolean]: TDataTime read GetWorkTime write SetWorkTime;
procedure MyClass.SetWorkTime(IsStart: Boolean;const value: TDataTime);
begin
mydataset.Edit;
if IsStart then
mydataset.FieldByName('work_start').AsDateTime := value else
mydataset.FieldByName('work_Finish').AsDateTime := value;
mydataset.Post;
end;
function MyClass.GetWorkTime(IsStart: Boolean): TTime;
begin
if IsStart then
Begin
if mydataset.FieldByName('work_start').IsNull then
fWorkStart := encodetime(6,0,0,0)
else
fWorkStart := mydataset.FieldByName('work_start').AsDateTime;
result := fWorkStart;
end else
begin
if mydataset.FieldByName('work_finish').IsNull then
fWorkfinish := encodetime(6,0,0,0)
else
fWorkfinish := mydataset.FieldByName('work_finish').AsDateTime;
result := fWorkfinish;
end
end;

Writing a generic TList of records

I am trying to write a generic TList that contains records of a specific type. Starting from David's answer on this question, I have written this class:
Type
TMERecordList<T> = Class(TList<T>)
Public Type
P = ^T;
Private
Function GetItem(Index: Integer): P;
Public
Procedure Assign(Source: TMERecordList<T>); Virtual;
Function First: P; Inline;
Function Last: P; Inline;
Property Items[Index: Integer]: P Read GetItem;
End;
Procedure TMERecordList<T>.Assign(Source: TMERecordList<T>);
Var
SrcItem: T;
Begin
Clear;
For SrcItem In Source Do
Add(SrcItem);
End;
Function TMERecordList<T>.First: P;
Begin
Result := Items[0];
End;
Function TMERecordList<T>.GetItem(Index: Integer): P;
Begin
If (Index < 0) Or (Index >= Count) Then
Raise EArgumentOutOfRangeException.CreateRes(#SArgumentOutOfRange);
Result := #List[Index];
End;
Function TMERecordList<T>.Last: P;
Begin
Result := Items[Count - 1];
End;
Having methods that return a pointer to the record works well (not perfectly) as pointers to records can be used as if they were records in most use cases. Using a record with properties and setters, these test cases work as expected:
TMETestRecord = Record
Private
FID: Word;
FText: String;
FValues: TIntegers;
Procedure SetID(Const Value: Word);
Procedure SetText(Const Value: String);
Procedure SetValues(Const Value: TIntegers);
Public
Property ID: Word Read FID Write SetID;
Property Text: String Read FText Write SetText;
Property Values: TIntegers Read FValues Write SetValues;
End;
// TestSetItem1
rl2[0] := rl1[0];
// TestSetItem2
r.ID := 9;
r.Text := 'XXXX';
r.Values := [9, 99, 999, 9999];
rl1[0] := r;
// TestAssignEmpty (rl0 is empty... after assign so should rl2)
rl2.Assign(rl0);
// TestAssignDeepCopies (modifications after assign should not affect both records)
rl2.Assign(rl1);
r.ID := 9;
r.Text := 'XXXX';
r.Values := [9, 99, 999, 9999];
rl1[0] := r;
Problem 1 - modifying a contained record
... this test case compiles and runs but does not work as desired:
// TestSetItemFields
rl1[0].ID := 9;
rl1[0].Text := 'XXXX';
rl1[0].Values := [9, 99, 999, 9999];
Modifications are applied to temporary copy of the record and not to the one stored in the list. I know this is a known and expected behaviour, as documented in other questions.
But... is there a way around it? I was thinking that maybe if the TMERecordList<>.Items property had a setter the compiler could maybe do what is actually desired. Could it? I know David has got a solution, as hinted at in this question... but I can't seem to find it on my own.
This would really be nice to have, as it would allow me to have a way of using the list identical (or almost) to that of a TList of objects. Having the same interface means I could easily change from objects to records and viceversa, when the need arises.
Problem 2 - interface ambiguity
Having the TList<> return a record pointer does pose some interface ambiguity problems. Some TList<> methods accept T parameters, and we know that being records, these are going to be passed by value. So what should these methods do? Should I rethink them? I'm talking specifically about these sets of methods:
Remove and RemoveItem
Extract and ExtractItem
Contains IndexOf, IndexOfItem and LastIndexOf
There is some ambiguity as to how these should test contained items to see if they match the parameter record value. The list could very well contain identical records and this could become a source of bugs in user code.
I tried not deriving it from TList<>, so as not to have these methods, but it was a mess. I couldn't write a class similar to TList without also writing my own TListHelper. Unfortunately System.Generics.Collections's one has some needed fields that are private, like FCount, and cannot be used outside the unit.
Problem 1
Your Items property is not marked as being default. Hence your erroneous code is picking up the base class default property. Add the default keyword to your Items property:
property Items[Index: Integer]: P read GetItem; default;
Problem 2
This is really a consequence of deriving from TList<T>. I would not recommend doing that. Encapsulate an instance of TList<T> and therefore define the interface explicitly rather than inheriting it. Or implement the list functionality directly in your code. After all, it's not much more than a wrapper around a dynamic array.
For what it is worth my classes don't use TList<T> at all which is a decision that I was very happy with when Emba broke the class in a recent release.
In the recent versions TList<T> in System.Generics.Collections contains a List property that gives you direct access to the backing array of the list. You can use that to manipulate the records inside the list.

Resources