CloneExtensions
CloneExtensions copied to clipboard
Additional context in initialization functions.
It would be very helpful if the initialization functions had some additional context that could get passed in that provided data on where in the property graph this current initialization is occuring. Example:
new Dictionary<Type, Func<context, object, object>>();
var clone = source.GetClone(initializers);
In the initializer, if I could then do something like:
string[] currentPath = context.Path;
And have it return an array of strings that represent the current location of the initialization (aka: ["ObjectA", "PropertyA", "Index[0]"])
I know something like this would affect initialization performance, but I don't think it would necessarily affect "second time through" performance. What do you think?
Just out curiosity, why would you want this functionality? How would you use it?
So, in my project, I have classes that act as prototypes/templates for new objects. I need the ability to do some more advanced field replacement during cloning. Consider the following example:
public class PrototypeA {
public Guid Id { get; set; }
pubic RelatedId { get; set; }
}
public class PrototypeB {
public Guid Id { get; set; }
}
I want to be able to clone an instance of PrototypeA, but when clones "RelatedId", I want it to actually recursively clone an instance of PrototypeB with a new Id, and update the "RelatedId" on my PrototypeA instance to the Id of the newly created PrototypeB instance.
If I could pass in metadata to the initializer that was a list of property paths I wanted to behave this way (currently can), and if the cloning context was available during the initializer indicating which field was being worked on (currently can't), then I could do this custom replacement.
I know this was meant to be a "cloning" library, and not a "templating" library, but was just curious if such a change would not break what you are trying to do, and enable this more custom use case at the same time?
Just to be clear, I am not the author of CloneExtensions, I am just an interested second party. A code sample of what you are attempting to accomplish may go along way in communicating the behavior you are seeking. Context helps quite a bit.
-Jeff
@deipax Thanks for clarification. No problem. I am afraid this will confuse things further, but here goes nothing. Here is a slightly more concrete example of what I am currently doing (copy of some code I am currently using):
public class Cloner
{
private static Dictionary<Type, Func<object, object>> initializers = new Dictionary<Type, Func<object, object>>();
private readonly Game game;
public Cloner(Game game)
{
this.game = game;
initializers.Add(typeof(Guid), (guidObject) =>
{
if (game.Prototypes.TryGetValue((Guid)guidObject, out var prototype))
{
var entity = game.CreateEntity(prototype);
return entity.Id;
}
else
return guidObject;
});
}
public Entity Clone(Prototype prototype)
{
return prototype.Entity.GetClone(initializers);
}
}
The way this currently works is that when it is cloning, it is able to identify that the field needs to recursively clone due to the Guid being unique and being found in a Hashset that I have of prototype IDs. But I am limited to that technique. Instead, I would rather know what field is being hit (instead of what value is in there) to determine if I need to recursively clone. This allows me to do some more advanced groupings that I cannot currently do without knowing which field is actually being cloned.
Side Note: Since I cannot do this, I am actually leaning towards just cloning the entity, and then later doing another pass with some of my own expression tree logic to do these nested clones. It would just be great if I didn't have to do this, and could take advantage of the fact this library is already walking the entire graph.
Would exposing some sort of interface which, if implemented by your class would prevent the default logic from being used and instead use your implementation work? You could mark PrototypeA
with that interface and implement GetCustomClone
method which would do what you want without running regular let's clone all the fields and properties code from CloneExtensions? And from within that method of yours you could call GetClone
on properties you want to be cloned further down the graph.
I don't think that would help much, and doing the second pass solution that I am doing now would still feel easier to implement and cleaner to maintain. I am not heartbroken if the clone initializers can't gain some additional context to allow smarter decisions when cloning, but it would be a nice to have feature. If that setup sounds difficult to implement with the currently library and/or there are performance implications I am not grasping, then I would be okay closing this issue out. I just wanted to pose my wishlist item to see if it was possible.