I would like to cast a generic object as an interface, but I get an "Operator not applicable to this operand type" (this code is part of a custom component where I attach different Entity classes to be opened by them).
procedure OpenNewEntity(EntityClass: TClass);
begin
if not Supports(EntityClass, IEntity) then
raise Exception.Create(EntityClass.ClassName + ' is not an Entity');
(EntityClass.Create as IEntity).Open;
end;
I'm trying to use TClass instead of the real class that implements the interface because I don't want to add all the dependencies of that class to this unit.
Instead of TClass I have also tried to declare a class reference to classes implementing the interface, but it says "Class type required".
TEntityClass = class of IEntity;
Can I use a new instance of an interface from a class that implements it without adding dependencies to those classes ?.
I guess that won't work this way in most cases, because that would always call TObject.Create and not any constructor declared in the EntityClass.
A better approach would be to use Generics, which allows the compiler to catch any class that does not support IEntity
:
type
IEntity = interface
['{1B885059-58E5-4475-883F-6CD04FA01D40}']
procedure Open;
end;
type
TEntityFactory = record
public
class function OpenNewEntity<T: constructor, IEntity>: IEntity; static;
end;
class function TEntityFactory.OpenNewEntity<T>: IEntity;
begin
Result := T.Create;
Result.Open;
end;
The constraints put onto the Generic type T
guarantees that the class has a parameterless constructor and supports IEntity
.
Update:
As it turns out, you only have the class type in a variable, and thus cannot use the Generic approach. The tricky part is now to create the Entity instance in the appropriate way. Based on the code found in TJSONUnMarshal.ObjectInstance()
, an implementation of your procedure could look like this:
procedure OpenNewEntity(EntityClass: TClass);
var
ctx: TRttiContext;
entity: IEntity;
instance: TObject;
rType: TRttiType;
mType: TRTTIMethod;
metaClass: TClass;
begin
ctx := TRttiContext.Create;
rType := ctx.GetType(EntityClass);
if rType <> nil then
begin
for mType in rType.GetMethods do
begin
if mType.HasExtendedInfo and mType.IsConstructor and (Length(mType.GetParameters) = 0) then
begin
// invoke
metaClass := rType.AsInstance.MetaclassType;
instance := mType.Invoke(metaClass, []).AsObject;
if not Supports(instance, IEntity, entity) then
raise Exception.Create(EntityClass.ClassName + ' is not an Entity');
entity.Open;
Exit;
end;
end;
raise Exception.Create(EntityClass.ClassName + ' has no parameterless constructor');
end;
raise Exception.Create(EntityClass.ClassName + ' has no runtime type information');
end;