1

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 ?.

Marc Guillot
  • 6,090
  • 1
  • 15
  • 42

1 Answers1

2

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;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Uwe Raabe
  • 45,288
  • 3
  • 82
  • 130
  • That looks awesome @Uwe, but would you be so kind to show a sample of how to call the TEntityFactory ?. I'm not very used to generics, and I get a "Method 'NewEntity' requires explicit type arguments" when I try to call it like that :"`Entity := TEntityFactory.OpenNewEdit;`" (EntityClass is defined as TClass and contains a valid class that implements Entitty). – Marc Guillot Jul 19 '23 at 13:30
  • 1
    So you don't have the actual class type available for the call? In that case the generic approach may not be appropriate and you need to find a solution for the create problem. You should expand a bit describing more of your system to allow suggestions how to do that. Is there a common ancestor for these classes? – Uwe Raabe Jul 19 '23 at 13:51
  • No, at that point I only have a a reference to the actual class. I'm building a TEntityAction (derived from TAction), with a new property EntityClass of type TClass (I don't want the component to have dependencies on the actual classes), So now I can assign those Actions to the main cxRibbon items and they'll open their corresponding Entity, unless you have assigned a OnExecute event indicating that you want to open manually the Entity (to assign parameters, ...). I basically use those TEntityActions to link my Entities and the cxRibbon items, and interact how to open them. – Marc Guillot Jul 19 '23 at 14:23
  • 1
    Then you will have problems creating the instances calling the appropriate constructor. There is a way using extended RTTI for that. As a start you can find such an implementation in TJSONUnMarshal.ObjectInstance. – Uwe Raabe Jul 19 '23 at 14:25
  • Thanks, calling EntityClass.Create seems to work fine. It's when I try to cast the new instance as Entity to call the Open method that it doesn't allow that casting. I will check the class that you quoted, and I will also check to just not do that casting and just call the Open method through Rtti. – Marc Guillot Jul 19 '23 at 14:33
  • I added an example using RTTI. – Uwe Raabe Jul 19 '23 at 14:35