Via Cà Matta 2 - Peschiera Borromeo (MI)
+39 02 00704272
info@synaptica.info

Dynamic Type Management in Delphi Using RTTI

Digital Innovation Partner

Dynamic Type Management in Delphi Using RTTI

Delphi’s Run-Time Type Information (RTTI) system provides powerful capabilities for dynamic type management. By leveraging RTTI, developers can inspect, modify, and interact with types and their members at runtime, enabling more flexible and adaptable applications. This article explores how to manage types dynamically in Delphi using RTTI, focusing on three key aspects:

  1. Assigning a type to an RTTI variable.
  2. Determining the type contained within an RTTI variable.
  3. Accessing a property or a static method of the class defined by the RTTI variable.
  4. Assigning the RTTI type based on an instance of an object.

1. Assigning a Type to an RTTI Variable

To work with RTTI in Delphi, you first need to obtain a TRttiType instance that represents the type you want to work with. This can be done using the TRttiContext class.

Step-by-Step Example

Step 1: Import Necessary Units

Ensure that your Delphi project includes the necessary units:

uses
  System.SysUtils,
  RTTI;
    

Step 2: Assigning a Type to a RTTI Variable

Here’s how you can assign a specific class type, such as TClientDataSet, to a TRttiType variable:

procedure AssignRttiType;
var
  Context: TRttiContext;
  RttiType: TRttiType;
begin
  // Initialize the RTTI context
  Context := TRttiContext.Create;
  try
    // Assign the TRttiType to TClientDataSet
    RttiType := Context.GetType(TClientDataSet);

    if Assigned(RttiType) then
      Writeln('Successfully assigned TRttiType to TClientDataSet.')
    else
      Writeln('Failed to assign TRttiType.');
  finally
    // TRttiContext is automatically managed
  end;
end;
    

This procedure creates an RTTI context and retrieves the RTTI type information for the TClientDataSet class.

2. Determining the Type Contained Within an RTTI Variable

Once you have a TRttiType variable, you might need to determine what specific type it represents. This can be particularly useful when working with polymorphism or dynamic type scenarios.

Checking the Exact Type

To verify if the TRttiType represents an exact type, use the following approach:

procedure CheckExactType(RttiType: TRttiType);
begin
  if (RttiType is TRttiInstanceType) and 
     (TRttiInstanceType(RttiType).MetaclassType = TClientDataSet) then
  begin
    Writeln('RttiType represents exactly TClientDataSet.');
  end
  else
  begin
    Writeln('RttiType does not represent TClientDataSet.');
  end;
end;
    

Checking for Inherited Types

To determine if the TRttiType is TClientDataSet or a descendant of it, use the InheritsFrom method:

procedure CheckInheritsFrom(RttiType: TRttiType);
var
  Metaclass: TClass;
begin
  if RttiType is TRttiInstanceType then
  begin
    Metaclass := TRttiInstanceType(RttiType).MetaclassType;
    if Metaclass.InheritsFrom(TClientDataSet) then
      Writeln('RttiType is TClientDataSet or a descendant.')
    else
      Writeln('RttiType is neither TClientDataSet nor a descendant.');
  end
  else
  begin
    Writeln('RttiType is not an instance type.');
  end;
end;
    

3. Accessing a Property or a Static Method of the Class Defined by the RTTI Variable

With the RTTI type information, you can dynamically access and manipulate properties or invoke methods of the class. Below are examples demonstrating how to achieve this.

Accessing a Property

Suppose you want to set the Active property of a TClientDataSet instance dynamically:

procedure SetActiveProperty(RttiType: TRttiType; Instance: TObject; Value: Boolean);
var
  Prop: TRttiProperty;
begin
  if Assigned(RttiType) then
  begin
    Prop := RttiType.GetProperty('Active');
    if Assigned(Prop) and Prop.IsWritable then
    begin
      Prop.SetValue(Instance, Value);
      Writeln('Property "Active" set to ', BoolToStr(Value, True));
    end
    else
    begin
      Writeln('Property "Active" not found or not writable.');
    end;
  end;
end;
    

Invoking a Method

To invoke a method, such as Open, on a TClientDataSet instance:

procedure InvokeOpenMethod(RttiType: TRttiType; Instance: TObject);
var
  Method: TRttiMethod;
begin
  if Assigned(RttiType) then
  begin
    Method := RttiType.GetMethod('Open');
    if Assigned(Method) then
    begin
      Method.Invoke(Instance, []);
      Writeln('Method "Open" invoked successfully.');
    end
    else
    begin
      Writeln('Method "Open" not found.');
    end;
  end;
end;
    

Invoking a Static Method

If the class has a static method, you can invoke it as well. For example, to invoke a static method StaticMethod of TMyClientDataSet:

procedure InvokeStaticMethod(RttiType: TRttiType);
var
  Method: TRttiMethod;
begin
  if Assigned(RttiType) then
  begin
    Method := RttiType.GetMethod('StaticMethod');
    if Assigned(Method) and Method.IsStatic then
    begin
      Method.Invoke(nil, []);
      Writeln('Static method "StaticMethod" invoked.');
    end
    else
    begin
      Writeln('Static method "StaticMethod" not found.');
    end;
  end;
end;
    

4. Assigning the RTTI Type Based on an Instance of an Object

In addition to assigning RTTI types based on class references, you can also assign the RTTI type based on an instance of an object. This is particularly useful when working with object instances whose types are determined at runtime.

Assigning RTTI Type from an Object Instance

Here’s how you can assign the RTTI type based on an existing object instance:

procedure AssignRttiTypeFromInstance(Instance: TObject; out RttiType: TRttiType);
var
  Context: TRttiContext;
begin
  if Assigned(Instance) then
  begin
    Context := TRttiContext.Create;
    try
      // Assign RTTI type based on the instance's class type
      RttiType := Context.GetType(Instance.ClassType);
      Writeln('TRttiType assigned based on the instance of ', Instance.ClassName, '.');
    finally
      // TRttiContext is automatically managed
    end;
  end
  else
  begin
    RttiType := nil;
    Writeln('Instance is nil. RTTI type not assigned.');
  end;
end;
    

This procedure takes an object instance and assigns its RTTI type to the RttiType variable. This allows you to work with the RTTI information of the specific instance dynamically.

Comprehensive Example

The following example ties together assigning a type, checking its type, accessing its members, and assigning RTTI based on an object instance:

program RTTIDynamicTypeManagement;

{$APPTYPE CONSOLE}

uses
  System.SysUtils,
  RTTI,
  Datasnap.DBClient;

type
  TMyClientDataSet = class(TClientDataSet)
  public
    class procedure StaticMethod;
  end;

{ TMyClientDataSet }

class procedure TMyClientDataSet.StaticMethod;
begin
  Writeln('StaticMethod of TMyClientDataSet called.');
end;

procedure AssignRttiType(var RttiType: TRttiType);
var
  Context: TRttiContext;
begin
  Context := TRttiContext.Create;
  try
    // Assigning TClientDataSet type
    RttiType := Context.GetType(TClientDataSet);
    Writeln('TRttiType assigned to TClientDataSet.');
  finally
    // TRttiContext is automatically managed
  end;
end;

procedure AssignRttiTypeFromInstance(Instance: TObject; out RttiType: TRttiType);
var
  Context: TRttiContext;
begin
  if Assigned(Instance) then
  begin
    Context := TRttiContext.Create;
    try
      // Assign RTTI type based on the instance's class type
      RttiType := Context.GetType(Instance.ClassType);
      Writeln('TRttiType assigned based on the instance of ', Instance.ClassName, '.');
    finally
      // TRttiContext is automatically managed
    end;
  end
  else
  begin
    RttiType := nil;
    Writeln('Instance is nil. RTTI type not assigned.');
  end;
end;

procedure CheckType(RttiType: TRttiType);
begin
  // Check exact type
  if (RttiType is TRttiInstanceType) and 
     (TRttiInstanceType(RttiType).MetaclassType = TClientDataSet) then
    Writeln('Exact type: TClientDataSet.')
  else
    Writeln('Not TClientDataSet.');

  // Check inheritance
  if (RttiType is TRttiInstanceType) and 
     (TRttiInstanceType(RttiType).MetaclassType.InheritsFrom(TClientDataSet)) then
    Writeln('Type is TClientDataSet or a descendant.')
  else
    Writeln('Type is neither TClientDataSet nor a descendant.');
end;

procedure AccessMembers(RttiType: TRttiType; Instance: TObject);
var
  Prop: TRttiProperty;
  Method: TRttiMethod;
begin
  // Access and set property 'Active'
  Prop := RttiType.GetProperty('Active');
  if Assigned(Prop) and Prop.IsWritable then
  begin
    Prop.SetValue(Instance, True);
    Writeln('Property "Active" set to True.');
  end
  else
    Writeln('Property "Active" not found or not writable.');

  // Invoke method 'Open'
  Method := RttiType.GetMethod('Open');
  if Assigned(Method) then
  begin
    Method.Invoke(Instance, []);
    Writeln('Method "Open" invoked.');
  end
  else
    Writeln('Method "Open" not found.');

  // Invoke a static method if available
  Method := RttiType.GetMethod('StaticMethod');
  if Assigned(Method) and Method.IsStatic then
  begin
    Method.Invoke(nil, []);
    Writeln('Static method "StaticMethod" invoked.');
  end
  else
    Writeln('Static method "StaticMethod" not found.');
end;

begin
  try
    var
      RttiType: TRttiType;
      ClientDataSet: TClientDataSet;
      MyClientDataSet: TMyClientDataSet;
    begin
      // Assign RTTI type to TClientDataSet
      AssignRttiType(RttiType);

      // Check the type
      CheckType(RttiType);

      // Create an instance of TClientDataSet
      ClientDataSet := TClientDataSet.Create(nil);
      try
        // Access members
        AccessMembers(RttiType, ClientDataSet);
      finally
        ClientDataSet.Free;
      end;

      Writeln('---');

      // Assign RTTI type to TMyClientDataSet
      var Context: TRttiContext := TRttiContext.Create;
      try
        RttiType := Context.GetType(TMyClientDataSet);
        Writeln('TRttiType assigned to TMyClientDataSet.');

        // Check the type
        CheckType(RttiType);

        // Create an instance of TMyClientDataSet
        MyClientDataSet := TMyClientDataSet.Create(nil);
        try
          // Access members
          AccessMembers(RttiType, MyClientDataSet);
        finally
          MyClientDataSet.Free;
        end;
      finally
        Context.Free;
      end;

      Writeln('---');

      // Assign RTTI type based on an object instance
      MyClientDataSet := TMyClientDataSet.Create(nil);
      try
        AssignRttiTypeFromInstance(MyClientDataSet, RttiType);
        // Check the type
        CheckType(RttiType);
        // Access members
        AccessMembers(RttiType, MyClientDataSet);
      finally
        MyClientDataSet.Free;
      end;

    end;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.
    

Explanation of the Example

  • Assigning RTTI Type: The AssignRttiType procedure initializes a TRttiContext and assigns the RTTI type for TClientDataSet.
  • Assigning RTTI Type from an Instance: The AssignRttiTypeFromInstance procedure takes an object instance and assigns its RTTI type to the RttiType variable.
  • Checking the Type: The CheckType procedure verifies if the RTTI type is exactly TClientDataSet and if it inherits from TClientDataSet.
  • Accessing Members: The AccessMembers procedure demonstrates how to set the Active property, invoke the Open method, and call a static method StaticMethod if it exists.
  • Handling Subclasses: The example also defines a subclass TMyClientDataSet and repeats the RTTI operations to showcase inheritance handling.
  • Assigning RTTI Based on Instance: Finally, the example shows how to assign the RTTI type based on an instance of TMyClientDataSet and perform the same operations dynamically.

Expected Output

TRttiType assigned to TClientDataSet.
Exact type: TClientDataSet.
Type is TClientDataSet or a descendant.
Property "Active" set to True.
Method "Open" invoked.
Static method "StaticMethod" not found.
---
TRttiType assigned to TMyClientDataSet.
Not TClientDataSet.
Type is TClientDataSet or a descendant.
Property "Active" set to True.
Method "Open" invoked.
Static method "StaticMethod" invoked.
---
TRttiType assigned based on the instance of TMyClientDataSet.
Exact type: TMyClientDataSet.
Type is TClientDataSet or a descendant.
Property "Active" set to True.
Method "Open" invoked.
Static method "StaticMethod" invoked.
    

Best Practices and Considerations

  • Type Safety: Always verify the type before performing operations to prevent runtime errors.
  • Performance: While RTTI is powerful, excessive use can impact performance. Use it judiciously.
  • Memory Management: TRttiContext is lightweight and managed automatically, but always ensure that dynamically created instances are properly freed.
  • Error Handling: Incorporate robust error handling when accessing members dynamically to handle cases where properties or methods may not exist.
  • Use with Caution: Dynamic type management adds flexibility but can make the code harder to understand and maintain. Use RTTI when it provides clear benefits.
Note: RTTI capabilities have been significantly enhanced in recent Delphi versions. Ensure your development environment supports the necessary RTTI features for your use case.

Conclusion

Delphi’s RTTI system provides a versatile framework for dynamic type management, enabling developers to write more flexible and adaptable code. By understanding how to assign types to RTTI variables, determine their types at runtime, and interact with their properties and methods dynamically, you can harness the full potential of RTTI in your Delphi applications.

Additionally, assigning RTTI types based on object instances allows for even greater flexibility, especially in scenarios where types are not known until runtime. Embracing RTTI not only enhances your ability to handle dynamic scenarios but also contributes to building more maintainable and scalable software solutions.