Replication

I must say, I am still pretty exited by the extended RTTI in Delphi 2010. It makes life so much easier in many circumstances. “Cloning” (I call it replication) is one of those. Say hello to TReplicator<T> (in DeHL.Replication). It can take any type and create an exact copy (including deep cloning for complex types).

The following example should pretty much explain what and how:

uses
  SysUtils,
  DeHL.Replication,
  DeHL.Collections.List;

type
  { Define a test class }
  TData = class
    { Pointer to self }
    FSelf: TObject;

    { A chunck of bytes }
    FSomeData: TBytes;

    { Some dummy string }
    FName: string;

    { An internal pointer. Marked as non-replicable -- will not be copied }
    [NonReplicable]
    FInternalPtr: Pointer;

    { This field will be copied by reference. It means that the new
      object will have the reference to the same TList<String> and not
      a copy of it. }
    [ByReference]
    FList: TList<String>;
  end;

var
  LInput, LOutput: TData;
  LReplicator: TReplicator<TData>;
begin
  { Create the input }
  LInput := TData.Create;
  LInput.FSelf := LInput;
  LInput.FSomeData := TBytes.Create(1, 2, 3, 4, 5, 6);
  LInput.FName := 'Some Name';
  LInput.FInternalPtr := Ptr($DEADBABE);
  LInput.FList := TList<String>.Create(['Hello', 'World']);

  { Create the replicator }
  LReplicator := TReplicator<TData>.Create;

  { Create a copy of LInput }
  LReplicator.Replicate(LInput, LOutput);

  { ... }
end.

As you can see we take LInput and create a copy into LOutput. Also note the use of [NonReplicable] and [ByReference] attributes on two fields of TData class. NonReplicable forces the replication process to skip the field and ByReference forces a shallow copy of reference fields (objects, pointers to arrays and dynamic arrays).

For the ease of use, a TCloneableObject is defined in the same unit (DeHL.Replication). TCloneableObject provides a method called Clone() which returns a copy of the object:

uses
  SysUtils,
  DeHL.Replication;

type
  { Define a test class }
  TTestMe = class(TCloneableObject)
    { ... }
  end;

var
  LInput, LOutput: TTestMe;
begin
  { ... }
  LOutput := LInput.Clone() as TTestMe;
  { ... }
end.

Note: This is just a preview of a new feature finding it’s way into DeHL. It’s currently only in the SVN repository. As usual, I need to write unit tests and so on to make sure it actually has no bugs (or at leas not that many bugs ;) .

10 Responses to 'Replication'

  1. Jolyon Smith says:

    Yet another good demonstration of how the use and proliferation of attributes leads to a conflation and mingling of concerns, not a separation.

    In this case, objects now need to include attribute decorations of members to inform some other class, the interaction with which isn’t clear (or even predictable) by simple inspection of the class itself.

    i.e. someone writing TData cannot predict the interaction with TReplicator(), and the developer using TReplicator cannot know that TData is “compatible” with TReplicator usage.

    Makes for a great demo or classroom feature, but I for one would not want to use this on a complex, long lived project with a dynamic team composition (having to teach new team members the conventions of what attributes need to be used to decorate which aspects of a class for those times when some other member of the team might employ some other class in conjunction with the code they are working in…).

    Better instead to have an IReplicatable interface (oh wait, ICloneable is already a well recognized convention for this, so let’s stick to that). A class that implements ICloneable clearly supports cloning and can be cloned, and if a class doesn’t implement ICloneable then it clearly doesn’t and cannot be, because it was never designed to be.

    And all you need to tell each new team member is… the ICloneable interface needs to be implemented for classes that are required to support cloning.

  2. David Novo says:

    @Jolyon Smith

    “And all you need to tell each new team member is… the ICloneable interface needs to be implemented”

    And once you have simply told them this sentence, these mythical new team members magically know how to implement ICloneable according to your conventions.

    Lets move back from your world to the real one. You then start teaching your new team members your convention for cloning. Lets say you use some version of the Assign “pattern” to do this. So you teach them there are several types of properties

    1. those you dont want to assign in a clone. Leave those alone.
    2. those to assign by reference. Just copy the fields
    3. objects you want to assign by doing doing a deep copy. use the .Assign pattern on those as well.
    4. whatever else you do when cloning in your infrastructure.

    so you could manually write

    a.Assign(source.A)
    B.Assign(source.B)
    c:=source.C

    all up and down every class in your system

    or you could mark the A,B,C properties with certain attributes and have a replicator do the work.

    The attributes are not vague and the interaction is not unclear. The replicator understands certain attributes, just like the person who uses ICloneable understands a certain interface. The attributes should be clearly defined and named of course, as should interface methods and interface names.

    The benefit of attributes is that I can, for instance, get a report of all the assignable properties of my class, something you cannot do the “old fashioned” way. Or for debugging, the replicator can easily keep track of all the properties/classes it assigned/cloned and you can inspect the report for bugs. It is so much more flexible and useful than hard coding the properties to assign in the body of the method in a hierarchy.

    The only valid argument IMHO is speed. Of course, the attribute based approach is slower than a virtual method call. That has to be weighed against the convenience, flexibility and all around coolness of using attributes in this context.

  3. Gary Mueller says:

    Very nice work! Educational, functional, and looking forward to implementing. keep it up!

  4. alex says:

    @Jolyon Smith
    TCloneableObject implements interface ICloneable and exposes virtual method Clone(). So you can either implement cloning by hand or leave the default implementation that uses TReplicator (TCloner sounded pretty wrong to me btw).

  5. alex says:

    @David Novo
    Speed indeed. All type-independent solutions are known to be slower of course. There is a trade between execution speed and implementation speed. Speed-passionate programmers would still avoid pretty much everything that has to do with run-time type inspection.

  6. Paolo Biondi says:

    Maybe this is noob question but why do you need FSelf?

  7. alex says:

    @Paolo Biondi
    To prove a point. FSelf points to the original object. The new created object will have a field FSelf pointing to itself rather than the original object.

  8. alex says:

    @Jolyon Smith
    How much separation is enough? For example .NET has its SmlSerializer class and a set of attributes that allow controlling how properties get serialized to XML. Nobody seem to complain about separation of concern there. It’s probably because XmlSerializer is a part of the framework and thus viewed a part of the ecosystem in which the object lives.

    Going in the same direction: If you would derive your class from TCloneableObject (which uses TReplicator inside) would that create the same separation problem? _You_ know that you have derived from TCloneableObject so you know the implications, and, most of all, the attributes ([ByReference] and [NonReplicable]) are a direct part of your ecosystem. In this case, the “replication concern” is handled by your own object and not some external class.

Trackbacks/Pingbacks
  1. [...] From time to time, people ask how to make a deep copy of an existing instance of a class.  Well, using the new, super cool RTTI, Alex is on the case. [...]

Leave a Reply

Your email address will not be published.