in the previous ( remove empty strings from list ) question I asked about the removal of empty strings from a stringlist
....
// Clear out the items that are empty
for I := mylist.count - 1 downto 0 do
begin
if Trim(mylist[I]) = '' then
mylist.Delete(I);
end;
....
From the aspect of code design and reuse I would now prefer a solution being more flexible as :
MyExtendedStringlist = Class(TStringlist)
procedure RemoveEmptyStrings;
end;
Q : Can I use a class helper in this case ? How would this look like in contrast to designing a new class as above ?
A class helper is an excellent idea here. To make it more widely applicable you should choose to associate the helper with the least derived class to which the helper can apply. In this case that means TStrings.
The huge advantage over deriving a new class is that your helper methods are available for instances of TStrings that are not created by you. Obvious examples include the TStrings properties that expose the contents of memos, list boxes etc.
I personally would write a helper that offers a more general removal functionality using a predicate. For instance:
type
TStringsHelper = class helper for TStrings
public
procedure RemoveIf(const Predicate: TPredicate<string>);
procedure RemoveEmptyStrings;
end;
procedure TStringsHelper.RemoveIf(const Predicate: TPredicate<string>);
var
Index: Integer;
begin
for Index := Count-1 downto 0 do
if Predicate(Self[Index]) then
Delete(Index);
end;
procedure TStringsHelper.RemoveEmptyStrings;
begin
RemoveIf(
function(Item: string): Boolean
begin
Result := Item.IsEmpty;
end;
);
end;
More generally, TStrings is an excellent candidate for a class helper. It is missing quite a deal of useful functionality. My helper includes:
An AddFmt method that formats and adds in one go.
An AddStrings method that adds multiple items in one call.
A Contains method that wraps up IndexOf(...)<>-1 and presents a more semantically meaningful method to future readers of code.
A Data[] property, of type NativeInt, and matching AddData method, that wraps the Objects[] property. This hides the casts between TObject and NativeInt.
I'm sure there is more useful functionality that could be added.
You can use a HelperClass, but you should base on TStrings, which would offer more flexibility.
An Example could be:
type
TMyStringsClassHelper = class helper for TStrings
Procedure RemoveEmptyItems;
end;
{ TMyStringsClassHelper }
procedure TMyStringsClassHelper.RemoveEmptyItems;
var
i:Integer;
begin
for i := Count - 1 downto 0 do
if Self[i]='' then Delete(i);
end;
procedure TForm5.Button1Click(Sender: TObject);
var
sl:TStringList;
begin
sl:=TStringList.Create;
sl.Add('AAA');
sl.Add('');
sl.Add('BBB');
sl.RemoveEmptyItems;
Showmessage(sl.Text);
Listbox1.Items.RemoveEmptyItems;
Memo1.Lines.RemoveEmptyItems;
sl.Free;
end;
Related
I would like to use Gabriel Corneanu's jpegex, a class helper for jpeg.TJPEGImage.
Reading this and this I've learned that beyond Delphi Seattle you cannot access private fields anymore like jpegex does (FData in the example below). Poking around with the VMT like David Heffernan proposed is far beyond me. Is there any easier way to get this done?
type
// helper to access TJPEGData fields
TJPEGDataHelper = class helper for TJPEGData
function Data: TCustomMemoryStream; inline;
procedure SetData(D: TCustomMemoryStream);
procedure SetSize(W,H: integer);
end;
// TJPEGDataHelper
function TJPEGDataHelper.Data: TCustomMemoryStream;
begin
Result := self.FData;
end;
Today I found a neat way around this bug using the with statement.
function TValueHelper.GetAsInteger: Integer;
begin
with Self do begin
Result := FData.FAsSLong;
end;
end;
Besides that Embarcadero did a nice job building walls to protect the private parts and that's probably why they named it 10.1 Berlin.
Beware! This is a nasty hack and can fail when the internal field structure of the hacked class changes.
type
TJPEGDataHack = class(TSharedImage)
FData: TCustomMemoryStream; // must be at the same relative location as in TJPEGData!
end;
// TJPEGDataHelper
function TJPEGDataHelper.Data: TCustomMemoryStream;
begin
Result := TJPEGDataHack(self).FData;
end;
This will only work if the parent class of the "hack" class is the same as the parent class of the original class. So, in this case, TJPEGData inherits from TSharedImage and so does the "hack" class. The positions also need to match up so if there was a field before FData in the list then an equivalent field should sit in the "hack" class, even if it's not used.
A full description of how it works can be found here:
Hack #5: Access to private fields
By using a combination of a class helper and RTTI, it is possible to have the same performance as previous Delphi versions using class helpers.
The trick is to resolve the offset of the private field at startup using RTTI, and store that inside the helper as a class var.
type
TBase = class(TObject)
private // Or strict private
FMemberVar: integer;
end;
type
TBaseHelper = class helper for TBase // Can be declared in a different unit
private
class var MemberVarOffset: Integer;
function GetMemberVar: Integer;
procedure SetMemberVar(value: Integer);
public
class constructor Create; // Executed automatically at program start
property MemberVar : Integer read GetMemberVar write SetMemberVar;
end;
class constructor TBaseHelper.Create;
var
ctx: TRTTIContext;
begin
MemberVarOffset := ctx.GetType(TBase).GetField('FMemberVar').Offset;
end;
function TBaseHelper.GetMemberVar: Integer;
begin
Result := PInteger(Pointer(NativeInt(Self) + MemberVarOffset))^;
end;
procedure TBaseHelper.SetMemberVar(value: Integer);
begin
PInteger(Pointer(NativeInt(Self) + MemberVarOffset))^ := value;
end;
As you can see it requires a bit of extra typing, but compared to patching a whole unit, it is simple enough.
I have a class defined which contains only strings as properties, and I need to get the property name based on its value as in the example below. In the example there are only 3 properties, in the real life class there are almost 1000. The problem is that this class is heavily used, and I want to know if I can get the property name by its value in a faster way.
unit Unit5;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs,RTTI, StdCtrls, Diagnostics;
type
TConstDBElem = class
public
CCFN_1 : String;
CCFN_2 : String;
CCFN_3 : String;
constructor Create;
end;
TForm5 = class(TForm)
Memo1: TMemo;
Button1: TButton;
procedure Button1Click(Sender: TObject);
end;
var
Form5: TForm5;
Obj: TConstDBElem;
implementation
{$R *.dfm}
procedure TForm5.Button1Click(Sender: TObject);
var iPos:Integer;
timer:TStopwatch;
Function GetName(const DBElemInstance : TConstDBElem; valueName: string) : string;
var
vrttiContext: TRttiContext;
vrttiField : TRttiField;
vType : TRttiType;
begin
vType := vrttiContext.GetType(TConstDBElem);
for vrttiField in vType.GetFields do
if (vrttiField.GetValue(DBElemInstance).ToString = valueName) then
begin
result := vrttiField.Name;
end;
end;
begin
timer := TStopwatch.Create;
timer.Start;
Memo1.Lines.Clear;
for iPos := 0 to 100000 do
GetName(Obj,'TEST3');
timer.Stop;
Memo1.Lines.Add(FloatToStr(timer.Elapsed.TotalSeconds));
end;
constructor TConstDBElem.Create;
begin
CCFN_1 := 'TEST1';
CCFN_2 := 'TEST2';
CCFN_3 := 'TEST3' ;
end;
initialization
Obj := TConstDBElem.Create;
finalization
Obj.Free;
end.
Yes,I know this is a very bad design, and this should not be done like this. Is there any option to make this search faster?
When GetName() finds a match, it is not stopping its loop, so it keeps searching for more matches. Assigning a function's Result does not exit the function, like you clearly think it does. As such, GetName() ends up returning the last match, not the first match. The loop should be calling Exit when it finds the first match:
Function GetName(const DBElemInstance : TConstDBElem; valueName: string) : string;
var
vrttiContext: TRttiContext;
vrttiField : TRttiField;
vType : TRttiType;
begin
vType := vrttiContext.GetType(TConstDBElem);
for vrttiField in vType.GetFields do
if (vrttiField.GetValue(DBElemInstance).ToString = valueName) then
begin
result := vrttiField.Name;
Exit; // <-- add this
end;
end;
Alternatively, use the version of Exit() that takes a parameter:
Function GetName(const DBElemInstance : TConstDBElem; valueName: string) : string;
var
vrttiContext: TRttiContext;
vrttiField : TRttiField;
vType : TRttiType;
begin
vType := vrttiContext.GetType(TConstDBElem);
for vrttiField in vType.GetFields do
if (vrttiField.GetValue(DBElemInstance).ToString = valueName) then
begin
Exit(vrttiField.Name); // <-- assigns Result and exits at the same time
end;
end;
In your simple example, the time wasted to search 3 fields is hardly noticeable, but when searching 1000 fields, it makes a difference.
You state in a comment that the values never change at runtime. In which case you can simply build a single dictionary at startup that has the property values as the dictionary key, and the property name as the dictionary value.
I'm assuming that all instances of the class have the same property values. If that's not the case then you'll need one dictionary per instance.
It's a "bad design" because someone wrote a class that they're treating like a C-style Struct. As has been said already, there are NO PROPERTIES defined in the class, just a bunch of PUBLIC DATA MEMBERS, aka, "fields".
There's no encapsulation, so anything you do to change the structure could have far-reaching implications on any unit that uses this. I agree that replacing the IMPLEMENTATION with a TStringList or a TDictionary would be smart, but ... there are no interfaces to adhere to! You have 1000-odd hard-wired references to public data members.
(The last time I saw something like this was some code written by a bunch of VB programmers who wrote classes as if they were C-style structs containing public data members, and then they wrote external functions to access the data, just as you'd do in C. Only they buried business logic inside of the accessor methods as well, which causes random pieces of the code to make direct references to the data members of the class.)
Off-hand, I'd say you're totally mis-using the RTTI code. Sure, the optimizations above will improve performance, but so what? It's the wrong solution!
If you really want to refactor this (and you certainly should!), first I'd look to see how widespread the use of this poor class is by changing the public to private and see how many errors you get.
Then I'd derive it from TStringList, delete all of the local fields, and move the GetName function inside of the class:
type
TConstDBElem = class( TStringList )
public
constructor Create;
function GetName( aName : string ) : string;
end;
Now, if I'm interpreting your example correctly, you want to do this to initialize the object:
constructor TConstDBElem.Create;
begin
Add( 'TEST1=CCFN_1' );
Add( 'TEST2=CCFN_2' );
Add( 'TEST3=CCFN_3' );
end;
Then replace all of the references in other units with a call to obj.GetName():
function TConstDBElem.GetName( aName : string ) : string;
begin
Result := Values[aName];
end;
You're replacing a reference to obj.CCFN_1 (?) or GetName(obj,'TEST1') with obj.GetName('TEST1').
(Maybe I'm totally off-base at this point. Sorry, but I just don't get how you're using this class from the example, and it doesn't make a whole lot of sense to me anyway. It would make more sense if you said what you're mapping between. I mean ... who needs to look up a local field name from a value associated with it? And what do you do with it once you've found it? Whomever wrote this had to go through some incredible contortions to make the code work because s/he sure didn't understand OOP when this was designed!)
At this point, you will have succeeded in decoupling the clients of this class (other units) from its internal implementation, and replacing those references with calls to an interface (a method) defined in the class instead.
Then you can do some testing to see what happens if you change the implementation, like from a TStringList to a TDictionary. But no matter how you slice it, I cannot imagine that either the TStringList or TDictionary will be slower than how you're abusing the RTTI system in your example. :)
I got strings in database like 'TGroupBox' or 'TEdit' ... now I need to check element against them... how do I enumerate string to type?
I mean something like this:
mystr := 'TGroupBox';
If (page.Controls[0] is mystr) then ...
Of course it won't work, as error appears:
E2015 Operator not applicable to this operand type
How do I do that correctly?
You can verify that
page.Controls[0].ClassName = mystr
using the ClassName property.
But notice that this doesn't do exactly the same thing as the is operator. To see the difference, suppose you have a class TFruit and a subclass TApple. If myFruit is an instance of a TApple, then both myFruit is TApple and myFruit is TFruit will yield true. But of course, the ClassName will still only be TApple.
If you need the full functionality of the is operator, you can make use of the ClassParent property, as suggested by hvd:
function IsDerivedFrom(AClass: TClass; const AClassName: string): boolean;
begin
if not Assigned(AClass) then Exit(false);
result := SameText(AClass.ClassName, AClassName) or
IsDerivedFrom(AClass.ClassParent, AClassName);
end;
To get the class of an object, use the ClassType property:
IsDerivedFrom(page.Controls[0].ClassType, mystr);
The function you are looking for is GetClass located in System.Classes. Be aware that the class has to be registered.
System.Classes.GetClass
For the specific scenario in the question body the answer by Andreas Rejbrand (with assistance from hvd) is a good one. However, for the broader problem implied by the question title - how to I convert a string containing a class name to a class reference? - you can utilise extended RTTI in a new(ish) version of Delphi:
unit ClassLookupUtils;
interface
uses
System.SysUtils, System.Generics.Collections, System.Rtti;
type
RttiClassLookup = record
strict private
class var FMap: TDictionary<string, TClass>;
class destructor Destroy;
public
class function Find(const ClassName: string): TClass; static;
end;
implementation
class destructor RttiClassLookup.Destroy;
begin
FMap.Free;
end;
class function RttiClassLookup.Find(const ClassName: string): TClass;
var
RttiType: TRttiType;
RttiContext: TRttiContext;
begin
if FMap = nil then
begin
FMap := TDictionary<string, TClass>.Create;
for RttiType in RttiContext.GetTypes do
if RttiType is TRttiInstanceType then
FMap.AddOrSetValue(RttiType.Name.ToLowerInvariant, (RttiType as TRttiInstanceType).MetaclassType);
end;
if not FMap.TryGetValue(ClassName.ToLowerInvariant, Result) then
Result := nil;
end;
end.
In use:
var
MyStr: string;
MyStrClass: TClass;
begin
//...
MyStrClass := RttiClassLookup.Find(MyStr);
if MyStrClass <> nil then
for I := 0 to Page.ControlCount - 1 do
if Page.Controls[I].InheritsFrom(MyStrClass) then
begin
//...
end;
The background here is that SomeObj is SomeClass is implemented as (SomeObj <> nil) and SomeObj.InheritsFrom(SomeClass).
You have a good answer from #UweRaabe usingRTTIto getClassName.
A simple (and not very robust) hack without using RTTI would be to use the TComponent.Name property, which is a string, like this - without the is operator:
If (pos('GroupBox', page.Controls[0].name)>0 ) then ...
By default, a control gets the same name as the instance variable, so GroupBox1.name='GroupBox1'. You can either change your database entries to use the substr 'groupbox' or extract 'groupbox' from the type name string in your database.
That being said, if you've inherited this design approach of persisting type names as strings in a database and then using them at runtime to check the types of different components, then you're stuck with it, and so be it. But Delphi is a strongly typed, compiled language, so persisting type names as strings in a database and reading them at runtime and decoding them into Delphi types just doesn't "smell right" IMO. I would re-think this design if possible. Consider doing it all in Delphi using classOf type, enumerations, etc.
I have a procedure for sorting nodes in a node tree (VirtualTreeView)
All memory leaks, extracted from FMM4 report, are stored in objects of a class TMemoryLeakList(these are the list I want to sort), which are stored in a list of lists called TGroupedMemoryLeakList, and both TMLL and TGMLL extend TObjectList. If I want to keep the functionality of being able to chose between ascending and descending sort order and choosing between sorting by one of four different data types, I 'have to' implement EIGHT different comparison methods (4 sort types * 2 sort directions) which I pass on to the main sorting method, because my TMLL list extends TObjectList. The main sorting method look like this.
The values for the fields fSortType and fSortDirection are acquired from the GUI comboboxes.
One of those eight generic comparison functions looks like this.
The seven remaining are copy/pasted variations of this one.
Is there any rational way to refactor this huge amount of copy paste code and still keep the functionality of choosing a specific sort type and direction?
Nice question about refactoring, but I dislike the answer you presumably are looking for. There is nothing wrong with a few extra lines of code, or a few extra routines. Especially the latter in which case naming actively assist in more readability.
My advice would be: leave the design as you have, but shorten the code:
function CompareSizeAsc(Item1, Item2: Pointer): Integer;
begin
Result := TMemoryLeak(Item2).Size - TMemoryLeak(Item1).Size;
end;
function CompareSizeDesc(Item1, Item2: Pointer): Integer;
begin
Result := TMemoryLeak(Item1).Size - TMemoryLeak(Item2).Size;
end;
function CompareClassNameAsc(Item1, Item2: Pointer): Integer;
begin
Result := CompareStr(TMemoryLeak(Item1).ClassName,
TMemoryLeak(Item2).ClassName);
end;
procedure TMemoryLeakList.Sort;
begin
case FSortDirection of
sdAsc:
case FSortType of
stSize: inherited Sort(CompareSizeAsc);
stClassName: inherited Sort(CompareClassNameAsc);
stCallStackSize: inherited Sort(CompareCallStackSizeAsc);
stId: inherited Sort(CompareIdAsc);
end;
sdDesc:
case FSortType of
stSize: inherited Sort(CompareSizeDesc);
stClassName: inherited Sort(CompareClassNameDesc);
stCallStackSize: inherited Sort(CompareCallStackSizeDesc);
stId: inherited Sort(CompareIdDesc);
end;
end;
end;
You can't get it much smaller then this ánd preserve the same level of readability.
Of course, you could rewrite the Sort routine as suggested by Arioch 'The:
procedure TMemoryLeakList.Sort;
const
Compares: array[TSortDirection, TSortType] of TListSortCompare =
((CompareSizeAsc, CompareClassNameAsc, CompareCallStackSizeAsc,
CompareIdAsc), (CompareSizeDesc, CompareClassNameDesc,
CompareCallStackSizeDesc, CompareIdDesc));
begin
inherited Sort(Compares[FSortDirection, FSortType]);
end;
But then: why not rewrite the QuickSort routine to eliminate the need for separate compare routines?
Alternatively, you could add ownership to TMemoryLeak in which case you have a reference to the owning list and its sort direction and sort type, for use within óne single compare routine.
Use function pointers.
var comparator1, comparator2: function (Item1, Item2: Pointer): Integer;
function sortComplex (Item1, Item2: Pointer): Integer;
begin
Result := comparator1(Item1, Item2);
if 0 = Result then Result := comparator2(Item1, Item2);
end;
Then you GUI elements should behave like
case ListSortType.ItemIndex of
itemBySzie : comparator1 := sortBySizeProcAsc;
....
end;
DoNewSort;
PS: make sure that you correctly specify those pointers even before user 1st click any GUI element;
PPS: you can rearrange this even further like
type t_criteria = (bySize, byName,...);
t_comparators = array[t_criteria] of array [boolean {Descending?}]
of function (Item1, Item2: Pointer): Integer;
const comparator1table: t_comparators =
( {bySize} ( {false} sortBySizeProcAsc, {true} sortBySizeProcDesc),
{byName} ( {false} sortByNameProcAsc, ...
Then you would fill working pointers from that array constants
This is my solution. Apart from completely rewriting the two procedures I also added two 'static' variables to my TMemoryLeakList class, and removed the former instance variables of the same name. This way, they are globally accessible to the Sort function.
TMemoryLeakList=class(TObjectList)
class var fSortType :TMlSortType;
class var fSortDirection :TMLSortDirection;
...
end
procedure TMemoryLeakList.Sort;
begin
inherited sort(sortBySomethingSomething);
end;
function sortBySomethingSomething(Item1, Item2: Pointer): Integer;
var
a, b : string;
ret : Integer;
begin
ret := 1;
if(TMemoryLeakList.fSortDirection = sdAsc) then
ret := -1;
case TMemoryLeakList.fSortType of stSize:
begin
a := IntToStr(TMemoryLeak(Item1).Size);
b := IntToStr(TmemoryLeak(Item2).Size);
end;
end;
case TMemoryLeakList.fSortType of stClassName:
begin
a := TMemoryLeak(Item1).ClassName;
b := TMemoryLeak(Item2).ClassName;
end;
end;
case TMemoryLeakList.fSortType of stID:
begin
a := IntToStr(TMemoryLeak(Item1).ID);
b := IntToStr(TMemoryLeak(Item2).ID);
end;
end;
case TMemoryLeakList.fSortType of stCallStackSize:
begin
a := IntToStr(TMemoryLeak(Item1).CallStack.Count);
b := IntToStr(TMemoryLeak(Item2).CallStack.Count);
end;
end;
//...jos tu
if a=b then
Result:=0
else if a>b then
Result:=-1*ret
else if a<b then
Result:=1*ret;
end;
I would like to rewrite this solution so as to use instance bounded variables fSortType,fSortDirection in TMemoryLeakList, but it seems impossible to pass a member function to an inherited sort function (from TObjectList), or is it?
I'm trying to create a generic class to which I can use a set of enums to initiate the values inside. For example:
constructor TManager<TEnum>.Create;
var
enum: TEnum;
enumObj: TMyObject;
begin
fMyObjectList:= TObjectDictionary<TEnum,TMyObject>.Create([doOwnsValues],10);
for enum:= Low(TEnum) to High(TEnum) do
begin
enumObj:= TMyObject.Create();
fMyObjectList.Add(enum, enumObj);
end;
end;
Additionally, later methods will fetch objects, via the enum value, for example:
function TManager<TEnum>.Fetch(enum: TEnum): TMyObject;
begin
fMyObjectList.TryGetValue(enum, Result);
end;
However, passing as a generic parameter, delphi doesn't know that TEnum is going to be an enum. Can I enforce that in some way?
As David mentioned the best you can do is at runtime with RTTI.
type
TRttiHelp = record
class procedure EnumIter<TEnum {:enum}>; static;
end;
class procedure TRttiHelp.EnumIter<TEnum {:enum}>;
var
typeInf: PTypeInfo;
typeData: PTypeData;
iterValue: Integer;
begin
typeInf := PTypeInfo(TypeInfo(TEnum));
if typeInf^.Kind <> tkEnumeration then
raise EInvalidCast.CreateRes(#SInvalidCast);
typeData := GetTypeData(typeInf);
for iterValue := typeData.MinValue to typeData.MaxValue do
WhateverYouWish;
end;
Although I don't know how the code behaves when your enum has defined values such as
(a=9, b=19, c=25)
Edit:
If you would like to return iterValue to the enum, you may use the following function, taken from a enum helper class by Jim Ferguson
class function TRttiHelp.EnumValue<TEnum {:enum}>(const aValue: Integer): TEnum;
var
typeInf: PTypeInfo;
begin
typeInf := PTypeInfo(TypeInfo(TEnum));
if typeInf^.Kind <> tkEnumeration then
raise EInvalidCast.CreateRes(#SInvalidCast);
case GetTypeData(typeInf)^.OrdType of
otUByte, otSByte:
PByte(#Result)^ := aValue;
otUWord, otSWord:
PWord(#Result)^ := aValue;
otULong, otSLong:
PInteger(#Result)^ := aValue;
else
raise EInvalidCast.CreateRes(#SInvalidCast);
end;
end;
You may then use the generically provided as the index to the dictionary in your constructor.
You cannot constrain a generic parameter such that low() and high() can be used in the generic class. The only constraints available are class or interface constraints.
To the very best of my knowledge, the language offers no generic way to enumerate over a generic enumerated type. Probably the best you can do is to use RTTI, sacrificing compile time type safety (as illustrated by Tobias).
Having read the comments to Tobias's answer, it seems likely that what you really want here is TObjectDictionary<TEnum,TMyObject>. That's because you want to be able to find a TMyObject instance given a TEnum key. And you want TObjectDictionary rather than TDictionary because the former takes over ownership of the TMyObject instances. You need somebody to free them all when you are done, and you may as well let TObjectDictionary do it for you.
For an example of the ownership side of this, see #RRUZ's answer here: Example for using Generics.Collections.TObjectDictionary