3

Is it possible to clone an object with another reference?

Here is an example what i'm trying to do: I have a TLabel named Label1. Now I want to create a Label2, equals Label1, able to be changed without reflect each other.

Ps: I'm using TLabel as an example, I want to copy any object in another instance.

In the code below, I've tried changing the name, but the reference still the same, when a change one of them the other changes too.

var
    cloneOfLabel1: TLabel;
begin
    Label1.Caption := 'label 1';
    cloneOfLabel1 := Label1;
    cloneOfLabel1.Name := 'label2';
    cloneOfLabel1.Caption := 'label 2';
    cloneOfLabel1.Left := 0;
    cloneOfLabel1.Top := Label1.Top+100;
Jerry Dodge
  • 26,858
  • 31
  • 155
  • 327
WellingtonD
  • 165
  • 4
  • 17

2 Answers2

8

That's possible to write Clone function for TComponent descendants:

function CloneComponent(aSource: TComponent): TComponent;
var mem: TMemoryStream;
begin
  mem := TMemoryStream.Create;
  try
    mem.WriteComponent(aSource);
    mem.Seek(0,soFromBeginning);
    Result := mem.ReadComponent(nil);
  finally
    mem.free;
  end;
end;

Note that only published properties gets copied that way and also some data which component saves through DefineProperties. Integers, floats, strings, enumerations and sets are cloned fine, but there could be problems with references: streaming system is designed for saving to file and reading from there, so no memory adresses get transfered, instead rather complicated actions are made to convert them into string paths including component owner and names. Usually you need to stream all the structure and only then some inner relationships would be saved.

If you are designer of classes which should be cloned, then overriding Assign method seems like more robust solution.

Good thing of this TComponent streaming solution is: you're able to clone some arbitrary component without knowing ahead, what class it is. For example, make a list of various components or some more complex structure and then get the full copy of each of them.

Yuriy Afanasenkov
  • 1,440
  • 8
  • 12
  • "streaming system is designed for saving to file" Indeed, so this is considered a hack. I would personally never rely on this, unless I was actually creating some sort of form designer myself. Of course, we have no idea what OP is really trying to accomplish - perhaps there is a real need for arbitrary objects. But like you said yourself, it only applies to `published` properties, so it would only work for things inherited from `TComponent` in the first place. – Jerry Dodge Sep 02 '17 at 22:23
  • 4
    @JerryDodge this is, in fact, how the IDE itself implements copy/paste of components at design-time. It streams a copied component to a DFM, then streams the DFM into the pasted component. This is the only official way that VCL and FMX support cloning objects without requiring every class to implement `TPersistent.Assign() ` and/or `TPersistent.AssignTo()` – Remy Lebeau Sep 03 '17 at 02:29
5

In your code, you are doing this:

cloneOfLabel1 := Label1;

However, this is merely acquiring a copy of its pointer, so it will still reference the original. It will not make a copy.

The closest you can get stems from TPersistent.Assign(), which needs to be implemented to actually copy the properties over. TLabel does not implement Assign, and so you cannot use it to create a clone either.

The answer is that you cannot arbitrarily clone objects, unless it inherits from TPersistent and implements Assign. And even then, you are at the mercy of the Assign procedure, so it will only copy those properties it was designed to copy. Name is certainly not one of those, especially since it's impossible to have two components with the same name. In fact, even if you do set a unique name to a cloned copy of a component, you cannot reference it by that, because it was created in run-time. Only design-time components can be referenced directly by their name.

On a side note, records can (by nature) be copied by using a simple :=, because records are value types instead of classes. However, I'm sure based on your example, that records are out of the question.

Jerry Dodge
  • 26,858
  • 31
  • 155
  • 327