chkaufmann 17 Posted December 2, 2020 I have a generic method that requires an enumeration type and a value in this enumeration. Right now my function definition looks like this: procedure Foo(ASetType: PTypeInfo; ADefault: Integer); This works fine. With GetTypeData() I can extract what I need (number of elements in enumeration). However, in the usage of this function I have to add TypeInfo() and Ord() each time: type TSet1 = (a11, a12, a13); TSet2 = (a21, a22, a23) then I can call: Foo(TypeInfo(TSet1), Ord(a12)); Foo(TypeInfo(TSet2, Ord(a23)); My question is, how to define the two parameters in order to avoid the TypeInfo() and the Ord() each time? Christian Share this post Link to post
FPiette 383 Posted December 2, 2020 32 minutes ago, chkaufmann said: I have a generic method I don't see any generics here. Not sure I fully understand what you need. Anyway here is what I think is a solution: type TSet1 = (a11, a12, a13); TSet2 = (a21, a22, a23, a33); TFoo<T: record> = class public class procedure Foo(ADefault : Integer); end; implementation class procedure TFoo<T>.Foo(ADefault: Integer); var Info : PTypeInfo; NumberOfElement : Integer; begin Info := TypeInfo(T); NumberOfElement := Info.TypeData.MaxValue - Info.TypeData.MinValue + 1; Form1.Memo1.Lines.Add(NumberOfElement.ToString); end; // Example use procedure TForm1.Button1Click(Sender: TObject); begin TFoo<TSet1>.Foo(123); TFoo<TSet2>.Foo(456); end; 1 Share this post Link to post
chkaufmann 17 Posted December 2, 2020 Yes "generic" is maybe the wrong word because I don't mean a generic method. Maybe I should have said untyped or typeless. I would just like to call it like this: Foo(TSet1, a13); Foo(TSet2, a21); Christian Share this post Link to post
Lars Fosdal 1792 Posted December 2, 2020 I am still not sure what you want to achieve or what it is that is the actual problem? Do you need to validate that the second parameter is valid for the first parameter? Or is it converting to/from an integer? if it compiles, aValue is a member of T function TFoo.IsMember<T>(const aValue:T): Boolean; begin Result := True; end; Convert it to an integer? function TFoo.ToInteger<T>(const aValue:T): Integer var V: TValue; begin V := TValue.From<T>(aValue); Result := V.AsOrdinal; end; 1 Share this post Link to post
chkaufmann 17 Posted December 2, 2020 It's just a question of readability. My version works fine, but now I would like to avoid these TypeInfo() and Ord() when I call Foo(). And if I can validate the second parameter to be a valid member of the first (tkEnumeration) Parameter, this would be a nice additional check. Christian Share this post Link to post
FPiette 383 Posted December 2, 2020 So you mean your remove type check at compilation time to do it at run time. Poor design. It would probably be a good idea that you explain WHY you need such construct. Maybe there are other solution than the [flawed?] one you designed. Share this post Link to post
chkaufmann 17 Posted December 2, 2020 Ok, here is the long story. In my system I define catalogs that can be used as lookup for values. There are complex catalogs with dynamic content that can be edited by the users. And there are simple catalogs that are hard coded in my application. For these hard coded catalogs I have builder class: TBSCatalogBuilder = class abstract strict protected function AddItem(AItemId: Integer; const AName: String; AIsDefault: Boolean = False; const AParentItem: IBSCatalogItem = nil): IBSCatalogItem; procedure DoBuildItems; virtual; abstract; public constructor Create(ACatalogId: Integer; const AName: String; AOptions: TBSCatalogOptions = []); end; So to define a catalog I create a subclass of TBSCatalogBuilder, overwrite the DoBuildItems method and call AddItem several times to define the catalog. Now some hard coded catalogs just represent the items of an enumaration type and instead of creating a subclass I would like one class with a constructor that takes the type of the enumeration and a second parameter with the default value. Right now the definition looks like this: TBSCatalogBuilderSetType = class sealed(TBSCatalogBuilder) strict protected procedure DoBuildItems; override; final; public constructor Create(ACatalogId: Integer; const AName: String; ASetType: PTypeInfo; ADefault: Integer = 0); end; and then I can use it like this: type TBSColumnType = (ctUnknown, ctBoolean, ctBytes, ctDateTime, ctFloat, ctInteger, ctMemo, ctString, ctGuid); begin myColumnTypeCatalog = TBSCatalogBuilderSetType.Create(1, 'Column Types', TypeInfo(TBSColumnType), Ord(ctString)); end; This works and it does what I want, but the TypeInfo() and the Ord() just don't look nice and I was wondering if there is not a way to avoid these. And I didn't want to define a generic class because like this the code is duplicated for every catalog. But maybe this is the only way to do it: TBSCatalogBuilderSet<T> = class(TBSCatalogBuilder) public constructor Create(ACatalogId: Integer; const AName: String; ADefault: T); end; Christian Share this post Link to post
FPiette 383 Posted December 2, 2020 A catalog looks like a StringList, isn't it? Could you provide minimal but complete source code to build and use TWO catalogs? Share this post Link to post
Lars Fosdal 1792 Posted December 2, 2020 This brings up my pet peeve with Generics - the lack of an Enumeration constraint for the generic type T. If you put something else than an enumeration as T in the call, it will blow up, but putting <T> on the methods allows you to do what you want? If you are running multiple sets, but the class basically does the same, can you use this? TBSCatalogBuilderSet = class(TBSCatalogBuilder) public // constructor Create; // Does what you need it do to, but stays away from the generics bits procedure Build<T>(ACatalogId: Integer; const AName: String; ADefault: T); end; procedure TBSCatalogBuilderSet.Build<T>(ACatalogId: Integer; const AName: String; ADefault: T) var V: TValue; Ordinal: Integer; begin V := TValue.From<T>(aDefault); Ordinal := V.AsOrdinal; // You already have the TypeInfo example above for extracting the range of T, or the name of the type as well // so the rest of your magic happens here end; Share this post Link to post
Remy Lebeau 1396 Posted December 2, 2020 (edited) 4 hours ago, Lars Fosdal said: This brings up my pet peeve with Generics - the lack of an Enumeration constraint for the generic type T. A record constraint can be used so that only value types can be specified for T, and then RTTI used to verify that T is actually an enumeration type (that check can be done at compile-time in XE7+). Edited December 2, 2020 by Remy Lebeau Share this post Link to post
chkaufmann 17 Posted December 2, 2020 This is what I did. The builder class itself has no RTTI. But I created a generic record. IBSLookupElements is a simple collection of (Key, Caption, ParentKey) elements. Fmt() and Translate() are string helper methods for Format and language specific translations. Then TBSCatalogBuilderSimple is a general subclass of my TBSCatalogBuilder class. Here is my record definition: type TBSCatalogBuilder<T> = record strict private FCatalogId : Integer; FDefault : Integer; FName : String; FTextIdent : String; FTypeData : PTypeData; public constructor Create(ACatalogId: Integer; const AName: String); function Gen: TBSCatalogBuilder; function SetDefault(const AValue: T): TBSCatalogBuilder<T>; function SetTextIdent(const AValue: String): TBSCatalogBuilder<T>; end; { TBSCatalogBuilder<T> } constructor TBSCatalogBuilder<T>.Create(ACatalogId: Integer; const AName: String); var i : PTypeInfo; begin i := TypeInfo(T); Assert(i.Kind = tkEnumeration, 'Passed type is not an enumeration.'); FTypeData := GetTypeData(i); FCatalogId := ACatalogId; FDefault := 0; FName := AName; end; function TBSCatalogBuilder<T>.Gen: TBSCatalogBuilder; var tmp : IBSLookupElements; begin tmp := NewBSLookupElements; for var ix := (FTypeData.MinValue + 1) to FTypeData.MaxValue do tmp.AddOrSet(FTextIdent.Fmt([ix]).Translate, ix); Result := TBSCatalogBuilderSimple.Create(FCatalogId, FName, tmp, FDefault); end; function TBSCatalogBuilder<T>.SetDefault(const AValue: T): TBSCatalogBuilder<T>; begin FDefault := TValue.From<T>(AValue).AsOrdinal; Result := Self; end; function TBSCatalogBuilder<T>.SetTextIdent(const AValue: String): TBSCatalogBuilder<T>; begin FTextIdent := AValue + '%d'; Result := Self; end; Then the usage of the helper looks like this: type TElmeSchlafmodus = (esmNone, esmKeinSchlafmodus, esmTeilzeit, esmVollzeit); ADef.RegisterCatalog(TBSCatalogBuilder<TElmeSchlafmodus>.Create(CAT_ELME_BETRIEB_SCHLAFMODUS, 'Schlafmodus') .SetTextIdent('#ElmeBetrieb.Schlafmodus') .SetDefault(esmVollzeit).Gen); Thanks for all help. Christian Share this post Link to post
Remy Lebeau 1396 Posted December 2, 2020 (edited) 2 hours ago, chkaufmann said: i := TypeInfo(T); Assert(i.Kind = tkEnumeration, 'Passed type is not an enumeration.'); FTypeData := GetTypeData(i); ... You should use the intrinsic GetTypeKind() function instead, eg: constructor TBSCatalogBuilder<T>.Create(ACatalogId: Integer; const AName: String); begin if GetTypeKind(T) <> tkEnumeration then begin raise Exception.Create('Passed type is not an enumeration.'); end else begin FTypeData := GetTypeData(TypeInfo(T)); ... end; end; This way, if T is an enumeration type, the compiler will omit the raise statement from the final executable, and if T is not an enumeration then it will omit the rest of the constructor code leaving just the raise, eg: constructor TBSCatalogBuilder<AnEnumType>.Create(ACatalogId: Integer; const AName: String); begin FTypeData := GetTypeData(TypeInfo(AnEnumType)); ... end; constructor TBSCatalogBuilder<ANonEnumType>.Create(ACatalogId: Integer; const AName: String); begin raise Exception.Create('Passed type is not an enumeration.'); end; The way you wrote the code originally using Assert() does not allow the compiler to make that same optimization. Edited December 2, 2020 by Remy Lebeau 2 Share this post Link to post
Guest Posted December 3, 2020 (edited) 19 hours ago, chkaufmann said: I have a generic method that requires an enumeration type and a value in this enumeration. Right now my function definition looks like this: procedure Foo(ASetType: PTypeInfo; ADefault: Integer); ... Christian hi Christian, you can creae a "HELPER" for your SET of values and get the values that you need: and use it for pass to params for your funcions, of course, maybe it needs some adjusts! Helper for your SETs values unit uHelperToSets; interface type TSetValues = (svOne, svTwo, svThree, svFour, svFive); // 0..255 = 256 elements max type THelperSetValues = record helper for TSetValues // or a Class if needs more complex tasks! function fncValueAsByte: Byte; // 0..255 elements function fncValueInText: string; end; implementation uses System.TypInfo; // GetEnumNames() { THelperSetValues } function THelperSetValues.fncValueAsByte: Byte; begin result := Byte(Self); // Self is the TSetValues; end; function THelperSetValues.fncValueInText: string; begin result := GetEnumName(TypeInfo(TSetValues), Ord(Self)); Delete(result, 1, 2); // deleting "sv" prefix end; end. FormMain type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.dfm} uses uHelperToSets; procedure TForm1.Button1Click(Sender: TObject); var lValues: TSetValues; begin lValues := svFour; // starting from "0"... Caption := lValues.fncValueAsByte.ToString; Caption := Caption + ' - ' + lValues.fncValueInText; end; hug Edited December 3, 2020 by Guest Share this post Link to post
Fr0sT.Brutal 900 Posted December 17, 2020 (edited) On 12/3/2020 at 1:45 AM, Remy Lebeau said: You should use the intrinsic GetTypeKind() function instead, eg: Fantastic! This function is undocumented for some reason... I added it to my TEnum<T> implementation Edited December 17, 2020 by Fr0sT.Brutal Share this post Link to post
Mahdi Safsafi 225 Posted December 17, 2020 10 minutes ago, Fr0sT.Brutal said: Fantastic! This function is undocumented and couldn't be found in System for some reason... I added it to my TEnum<T> implementation It's an intrinsic and has no implementation ... the function is evaluated on the fly (no code will be generated). BTW, Spring4D makes extensively use of it. Share this post Link to post
Fr0sT.Brutal 900 Posted December 17, 2020 11 minutes ago, Mahdi Safsafi said: It's an intrinsic and has no implementation ... the function is evaluated on the fly (no code will be generated). BTW, Spring4D makes extensively use of it. Yep I already figured it out and modified the text. Anyway there's no any mention of this feature in docwiki Share this post Link to post
Remy Lebeau 1396 Posted December 17, 2020 (edited) 5 hours ago, Fr0sT.Brutal said: Anyway there's no any mention of this feature in docwiki There are quite a few intrinsic functions that have been around for awhile but are still not documented by Embarcadero, but they are by 3rd parties. For instance, https://stackoverflow.com/questions/30417218/. See https://www.google.com/search?q=delphi+undocumented+intrinsics Edited December 17, 2020 by Remy Lebeau 1 1 Share this post Link to post
bravesofts 6 Posted August 30, 2021 (edited) why you Use the Word "Set" Instead of "Enum" or "whatever words except Set" is it possible to call an "Enumeration Type" as a "Set Type" ? TSetValues = (svOne, svTwo, svThree, svFour, svFive); the Right declaration must be any name except this : TSetValues TEnumValues = (evOne, evTwo, evThree, evFour, evFive); Sets are declared like that: TSet_Of_Digits = set of '1'..'9'; i hope i'm not wrong !!! Edited August 30, 2021 by bravesofts clarify my remark Share this post Link to post
Rollo62 536 Posted August 30, 2021 42 minutes ago, bravesofts said: why you Use the Word "Set" Instead of "Enum" is it possible to call an "Enumeration Type" as a "Set Type" ? Not sure if you're concern is the term Set or Enum in the type name. I usually try to use singular, plural nomenclature anyway ( with or without Set/Enum mostly ). TMySpecialValue = (svOne, svTwo, svThree, svFour, svFive); TMySpecialValues = set of TMySpecialValue; or TMySpecialValueSet = set of TMySpecialValue; That could be mixed e.g. with array or list, but in the code it will be directly clear usually. Share this post Link to post