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:

procedure TDatafile.uploadList(db: TDataset; const value: TValue; fieldname: string);    function isUploadable(method: TRttiMethod): boolean;    var       param: TArray&lt;TRttiParameter&gt;;       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&lt;TDatafile&gt;;       db.Append;       datafile.upload(db);    end;    if db.state in dsEditModes then       db.Post;    enumerator.Free; end;

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?

Leave a Reply