Reputation: 2423
I have a thread library which has three constructors for three different method types. I want them to be merged into one and some kind of logic to tell them apart inside the constructor. Is it possible? As there is TValue
for values and such, is there a similar thing for method types?
I have the following types supported for now;
TAgThreadMethod1 = procedure of object;
TAgThreadMethod2 = procedure;
TAgThreadMethod3 = procedure(const AThread: TAgThread) of object;
and the constructors are like so:
constructor Create(const AOnRun: TAgThreadMethod1); overload; virtual;
constructor Create(const AOnRun: TAgThreadMethod2); overload; virtual;
constructor Create(const AOnRun: TAgThreadMethod3); overload; virtual;
For reference, I don't want the user to have the ability to change the worker method at a later time after the construction at all. So if a solution exists which can do something like this in a single constructor, is also welcome;
constructor Create
(const AOnRun: [Some type which can hold arbitrary method types]);
begin
// code to identify the method contained in AOnRun.
// if supported, assign it the correct handler.
end;
Upvotes: 5
Views: 230
Reputation: 223
It is possible, but not with only a TValue.
Instead of passing the method itself, you could pass it as a TRttiMethod like this: MyRegisterMethod(TRttiContext.GetType(TMyClassType).GetMethod('MyMethodName'));
If you want to pass all constructors you will need to use a loop like this:
for MyMethod in TRttiContext.GetType(TMyClassType).GetMethods do
if MyMethod.IsConstructor then // As in your example you only want constructors
MyRegisterMethod(MyMethod);
Later, you can call this method like this: MyRegisteredMethod.Invoke(nil, MyMethodParams)
. MyMethodParams is an array of TValue, which you will need to provide. This can be stored in a TValue itself (although it might be easier to just store them in a TArray). Note that you will only want to use nil
in the invoke in case of a class method.
If you want to make sure the parameters are OK, you can verify them by looking them up in MyRttiMethod.GetParameters
and compare their actual types to the actual types of the TValues in the array of TValue. This can also be used to find the correct method as well.
Upvotes: 1
Reputation: 612794
I think that the best that you can do is have a constructor that accepts the most general form of the procedural type. That is TProc<TAgThread>
. This will be the master constructor, the only virtual constructor. You can then delegate the other constructors to the master constructors.
To recap, the declaration in SysUtils
is:
type
TProc<T> = reference to procedure(Arg1: T);
So your master constructor is:
constructor Create(AOnRun: TProc<TAgThread>); overload; virtual;
The other constructors are not virtual and might look like this:
constructor Create(AOnRun: TAgThreadMethod1); overload;
Implemented as:
constructor TMyClass.Create(AOnRun: TAgThreadMethod1);
begin
Create(
procedure(Thread: TAgThread)
begin
AOnRun();
end;
);
end;
The master constructor, the virtual one, does all the work. The other constructors, the non-virtual ones, adapt their arguments to fit that of the master constructor.
Upvotes: 4
Reputation: 84540
There isn't any good way to do this, because the whole point of a method pointer is to be invoked at some later point, and you can't do that unless you know its signature. So losing the distinction between signatures is very counterproductive.
If you only want to have to store one type of call inside your object, you could make the three constructors each create an anonymous method with a unified signature that wraps calls to the three types, and just store that instead of having to deal with multiple distinct method types. But what you're asking for, specifically, won't work.
Upvotes: 5