Ali Dehban 38 Posted February 27 Hi mates, I have something on my mind but I couldn't implement it correctly, imagine a generic interface, some classes inherited from that interface, and one method in each class with the same name, now I'm trying to use this interface type every where for different approaches but it doesn't compile correctly. Please have a look at the code if you get a chance and share your thoughts with me, I do appreciate you in advance. The question is how can I implement such an idea properly and safely? I have attached a sample project to save you time too. unit Unit1; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, System.Rtti; type IMyInterface<T> = interface function DoSomething: T; end; TMyClass<T> = class(TInterfacedObject, IMyInterface<T>) function DoSomething: T; end; TForm1 = class(TForm) procedure FormCreate(Sender: TObject); private { Private declarations } function UseInterface<T>(obj: IMyInterface<T>): T; public { Public declarations } end; var Form1: TForm1; implementation {$R *.dfm} { TMyClass<T> } function TMyClass<T>.DoSomething: T; var ctx: TRttiContext; typ: TRttiType; begin ctx := TRttiContext.Create; typ := ctx.GetType(TypeInfo(T)); if typ.TypeKind = tkInteger then Result := 20 // E2010 Incompatible types: 'T' and 'Integer' else if typ.TypeKind = tkString then Result := T('Hello') // E2089 Invalid typecast else if typ.AsInstance.MetaclassType.InheritsFrom(TStringList) then Result := (typ.AsInstance.MetaclassType.InitInstance(typ) as TStringList) //E2010 Incompatible types: 'T' and 'TStringList' else Result := Default(T); ctx.Free; end; { TForm1 } function TForm1.UseInterface<T>(obj: IMyInterface<T>): T; begin Result := obj.DoSomething; end; procedure TForm1.FormCreate(Sender: TObject); var obj1: IMyInterface<Integer>; obj2: IMyInterface<String>; obj3: IMyInterface<TStringList>; begin try obj1 := TMyClass<Integer>.Create; obj2 := TMyClass<String>.Create; obj3 := TMyClass<TStringList>.Create; ShowMessage(UseInterface<Integer>(obj1).ToString); ShowMessage(UseInterface<String>(obj2)); ShowMessage(UseInterface<TStringList>(obj3).Text); except on E: Exception do Writeln('Exception: ', E.ClassName, ': ', E.Message); end; end; end. Generic Interface.zip 1 Share this post Link to post
PeterBelow 238 Posted February 27 Generics in Delphi don't work the way you seem to think they do. To use the generic type T inside a method of the generic class with a specific type the compiler needs some minimal information on what T can be. That is what generic constraints are for. Unfortunately the constraints supported at the moment are quite limited and will not cover what you intend to do. 2 Share this post Link to post
Ali Dehban 38 Posted February 27 First, thank you for your response. Second, I thought so but! It seems using RTTI's TValue it is possible to handle this situation somehow. I'm just not sure if everything goes right with this code or not at the end, I tested with "ReportMemoryLeaksOnShutdown := True;" and there is no memory leak. Please have a look at the final version and share your thoughts with me. Best regards. type IMyInterface<T> = interface ['{D68085A3-6AFB-46D9-A4EE-B46563758127}'] function DoSomething: T; end; TMyClass<T> = class(TInterfacedObject, IMyInterface<T>) function DoSomething: T; end; TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private { Private declarations } function UseInterface<T>(obj: IMyInterface<T>): T; public { Public declarations } end; var Form1: TForm1; implementation {$R *.dfm} { TMyClass<T> } function TMyClass<T>.DoSomething: T; var ctx: TRttiContext; typ: TRttiType; val: TValue; begin ctx := TRttiContext.Create; typ := ctx.GetType(TypeInfo(T)); if typ.TypeKind = tkInteger then val := 20 else if (typ.TypeKind = tkString) or (typ.TypeKind = tkUString) then val := 'Hello' else if typ.AsInstance.MetaclassType.InheritsFrom(TStringList) then begin val := TStringList.Create; val.AsType<TStringList>.Add('Hello from StringList'); end; Result := Val.AsType<T>; ctx.Free; end; { TForm1 } function TForm1.UseInterface<T>(obj: IMyInterface<T>): T; begin Result := obj.DoSomething; end; procedure TForm1.Button1Click(Sender: TObject); var obj1: IMyInterface<Integer>; obj2: IMyInterface<String>; obj3: IMyInterface<TStringList>; lvstr: TStringList; begin try try obj1 := TMyClass<Integer>.Create; obj2 := TMyClass<String>.Create; obj3 := TMyClass<TStringList>.Create; ShowMessage(UseInterface<Integer>(obj1).ToString); ShowMessage(UseInterface<String>(obj2)); lvstr := UseInterface<TStringList>(obj3); ShowMessage(lvstr.Text); except on E: Exception do ShowMessage('Exception: ' + E.ClassName + ': ' + E.Message); end; finally lvstr.Free; obj1 := nil; obj2 := nil; obj3 := nil; end; end; Generic Interface-final.zip 1 Share this post Link to post
PeterBelow 238 Posted February 28 Well, it may work but I fail to see the usefulness of such a construct. Share this post Link to post
Ali Dehban 38 Posted February 28 I see. Well, beyond the curiosity, it depends on the nature of the project. As a general sample, it could/might be useful to implement something like an ORM, especially in an Active-Record structure or like that. (More info about the Active-Record: https://guides.rubyonrails.org/active_record_basics.html) In my case, I need it in a multi-purpose data access layer of a project but with complex data types, not Integer and String parameters combined heavily to RTTI functionalities. Thank you for your feedback. Share this post Link to post
Eugine Savin 4 Posted March 2 On 2/27/2024 at 11:00 PM, Ali Dehban said: It seems using RTTI's TValue it is possible to handle this situation somehow. You can do it without TValue if TypeInfo(T) = TypeInfo(Integer) then PInteger(@Result)^ := 10 else if TypeInfo(T) = TypeInfo(string) then PString(@Result)^ := 'Hello' 2 Share this post Link to post
Remy Lebeau 1403 Posted March 2 In XE7 and later, you should use the GetTypeKind() intrinsic function instead of using TypeInfo() comparisons: https://delphisorcery.blogspot.com/2014/10/new-language-feature-in-xe7.html case GetTypeKind(T) of tkInteger: PInteger(@Result)^ := 10; tkLString: PAnsiString(@Result)^ := 'Hello'; tkUString: PUnicodeString(@Result)^ := 'Hello'; ... end; 3 Share this post Link to post
pmcgee 16 Posted April 8 (edited) @Ali Dehban I like the idea of the area you were investigating here. Stretching my brain to a function that can return any type is something I have thought about in the last year or so. It took a while before I started to wrap my head around enough it to have questions, 'tho. The first was that IMyInterface<T> = interface function DoSomething: T; end; looks a lot like the definition of an anonymous function ... and then I replaced it with records. But I'll skip that for now. My second eventual question is why have function UseInterface<T> ( obj: IMyInterface<T> ) : T; begin Result := obj.DoSomething; end; vs just ? obj.DoSomething; So, as a first step I ended up with the following : (I realise you would have been simplifying the code above from your real use case) {$APPTYPE CONSOLE} program Project3; uses System.SysUtils, System.Variants, System.Classes, System.Rtti; type IWithAny<T> = interface function DoSomething: T; end; TWithAny<T> = class(TInterfacedObject, IWithAny<T>) function DoSomething : T; end; { TWithAny<T> } function TWithAny<T>.DoSomething: T; begin var val: TValue := TValue.Empty; var typ: T := default(T); case GetTypeKind(T) of tkString, tkUString : val := 'Hello'; tkInteger : val := 20; tkClass : if TValue.From<T>(typ).IsType<TStringList> then begin val := TStringList.Create; val.AsType<TStringList>.Add('Hello from StringList'); end; end; Result := Val.AsType<T>; end; var obj1 : IWithAny< Integer >; obj2 : IWithAny< String >; obj3 : IWithAny< TStringList >; begin obj1 := TWithAny< Integer > .Create; obj2 := TWithAny< String > .Create; obj3 := TWithAny< TStringList >.Create; writeln( obj1.DoSomething ); writeln( obj2.DoSomething ); var lvstr := obj3.DoSomething; writeln(lvstr.Text); lvstr.Free; readln; ReportMemoryLeaksOnShutdown := True; end. What I'd kinda like to see is the interface return a (managed record?) holding the <T>, made to clean up it's own memory. Edited April 8 by pmcgee 1 Share this post Link to post
Ali Dehban 38 Posted April 8 On 3/3/2024 at 12:11 AM, Remy Lebeau said: GetTypeKind(T) @pmcgee Hi. In most scenarios your sample would be enough, I agree. But why I used this: function UseInterface<T> ( obj: IMyInterface<T> ) : T; begin Result := obj.DoSomething; end; Because I wanted to call ClassObj.DoSomething from within another class and using one function which is not necessary but I wanted to know how it works in Delphi. When you are calling something like these generic object's generic members/methods from within another object(different class) then Delphi forces you to write like that. For instance, something like this won't be compiled which was my desired call but it's not allowed: function UseInterface(obj: T): T; Besides, I wanted to limit the input type to classes (no primitive types at all) which is possible if I use such a function, so my final function is even more complicated like this: function UseInterface<T: class>(obj: IMyInterface<T>): T; The keyword "class" here can limit the input type only to classes, so Integer or String is no longer allowed here. Share this post Link to post
pmcgee 16 Posted April 8 @Ali Dehban can you expand on that ? I'm trying to picture why if you have an obj ( where the type = IMyInterface<xxx> ) anywhere in your code, you couldn't use obj.DoSomething . Isn't that the point of the interface? Share this post Link to post
Ali Dehban 38 Posted April 8 You're absolutely right, this is the normal usage, but at the moment you want to have one function to avoid repeating with the "obj" as input parameter then Delphi forces you to write that function in this way. As I mentioned this is not necessary and what you wrote is ok 99 percent of the time. There is one benefit with such a function that is the possibility to limit the input type to classes.(No primitive types like integer) I'm writing a library and I wanted to force the developers to use it in this way in this case. Share this post Link to post