RTTI and enumerators

I’ve been doing a lot of work with the new RTTI system lately, figuring out how to take an arbitrary object and upload its data to a TClientDataset, or serialize it to a file.  I’ve got it working, and most of it was pretty straightforward once I managed to wrap my brain around the RTTI system.

Getting inside a generic list, though, was a bit of a hack.  Since Delphi 2010 doesn’t support covariance and contravariance on generic classes, you can’t take a routine that accepts “a TList<T>” and enumerate over all its contents, even if you constrain T to only descendants of a specific base class.  What I ended up doing is kinda ugly:

[code lang="delphi"]
procedure TDatafile.uploadList(db: TDataset; const value: TValue; fieldname: string);

   function isUploadable(method: TRttiMethod): boolean;
   var
      param: TArray<TRttiParameter>;
      enumerator: TRttiType;
      prop: TRttiProperty;
      retval: TRttiType;
   begin
      result := false;
      param := method.GetParameters;
      if not (length(method.GetParameters) = 0) then
         Exit;
      enumerator := method.ReturnType;
      if not assigned(enumerator) then
         Exit;
      prop := enumerator.GetProperty('current');
      if not assigned(prop) then
         Exit;
      retval := prop.PropertyType;
      result := (retval is TRttiInstanceType);
   end;

var
   iType: TRttiInstanceType;
   enumMethod: TRttiMethod;
begin
   assert(value.Kind = tkClass);
   iType := FContext.GetType(value.TypeInfo) as TRttiInstanceType;
   enumMethod := iType.GetMethod('GetEnumerator');
   assert(assigned(enumMethod) and (isUploadable(enumMethod)));
   uploadEnumerable(db, value, enumMethod, fieldname);
end;

procedure TDatafile.uploadEnumerable(db: TDataset; const value: TValue;
  uploadMethod: TRttiMethod; fieldName: string);
var
   enumerator: TObject;
   enumType: TRttiType;
   current: TRttiProperty;
   moveNext: TRttiMethod;
   datafile: TDatafile;
begin
   enumerator := uploadMethod.Invoke(value, []).AsObject;
   enumType := FContext.GetType(enumerator.ClassInfo);
   current := enumType.GetProperty('Current');
   moveNext := enumType.GetMethod('MoveNext');

   while moveNext.Invoke(enumerator, []).AsBoolean do
   begin
      datafile := current.GetValue(enumerator).AsType<TDatafile>;
      db.Append;
      datafile.upload(db);
   end;
   if db.state in dsEditModes then
      db.Post;
   enumerator.Free;
end;
[/code]

There’s some unrelated code mixed in which is specific to the way I’m uploading the objects to the datasets, but this is mainly about enumerating a generic list of an unknown type.  Basically, we’re doing manually what the language-level enumerator support does automatically, extracting and validating the enumerator and running the enumeration by hand.  Scary, no?  There’s gotta be a better way.

What I’d really like to see is something like this.  TRttiStructuredType (the base class for TRttiRecordType, TRttiInstanceType and TRttiInterfaceType) should get two methods:

function TRttiStructuredType.IsEnumerable: boolean;

function TRttiStructuredType.GetEnumerator: TRttiEnumerator; //returns nil if IsEnumerable is false

The TRttiEnumerator would basically do what my code above does: crack open the underlying type’s enumerator and run it, returning an enumerated sequence of TRttiObject objects.  But in order for that to work well, (without the sort of ugly hacks you see above,) it needs compiler-level support.  Maybe we can hope to see it in D2011?

Comments are closed.