Clone object while retaining derived type from extension method

Not sure if my native language is right, so bear with me on that.

I have a public interface that is used in plugins, classes that implement this interface. I want to be cloned while keeping my declaration type and all properties.

I know about ICloneable

and I could just implement this in my interface, but I do not want to give up the requirement to implement the interface for plugin developers and I myself want to control it myself.

It is also worth noting that it should be light and should not be deep. I also don't know anything about a declared type at design time other than how it implements my plugin interface, so I would need to turn it into my "unknown" source type.

public interface ImyInterface
{
    int commonProp {get;set;}
}

// This class and the properties therein are not known at design time
public myObj : ImyInterface
{
    int commonProp {get;set;}
    int uncommonProp {get;set;}
}

      

Then I need to call from the application somthing like:

// This is how I generally "activate my plugins"
ImyInterface obj = (ImyInterface)Activator.CreateInstance(type);

// Then elsewhere I need to clone its current state.
var ClonedObj = obj.clone();

      

I tried this one , but for that I need to know the type at design time.

+3


source to share


1 answer


I recommend that you use DeepCloner which is available on NuGet. I think this library implements exactly what you need and as extension methods . It's also Open Source and hosted on GitHub, so if you want to add more functionality or just know how it works, you can check out the code.

From the project site:

In addition, there is no requirement to specify the type of object to clone. Object can be cast to inteface or as abstract object, you can clone ints array as abstract Array or IEnumerable, even null can be cloned without any error.

I made this sample to show how it works:

An interface and class that implements this interface:

 interface IPluginInterface { }

    class Foo:IPluginInterface
    {
        public int SomeInt { get; set; }
        public string SomeString { get; set; }

        public Foo()
        {
            SomeInt = 42;
            SomeString = "SomeString";
        }

        public override string ToString() => $"SomeInt: {SomeInt}. SomeString: {SomeString}";
    }

      

Then basically add using Force.DeepCloner; and...

static void Main(string[] args)
        {

            IPluginInterface foo = new Foo();
            IPluginInterface fooWithActivator = (IPluginInterface) Activator.CreateInstance(typeof(Foo));
            Console.WriteLine(foo);
            var cloneOfFoo = foo.DeepClone();
            var cloneOfFooWithActivator = fooWithActivator.DeepClone();

            Console.WriteLine(cloneOfFoo);
            Console.WriteLine(cloneOfFoo == foo);
            Console.WriteLine(cloneOfFoo.GetType());

            Console.WriteLine(cloneOfFooWithActivator);
            Console.WriteLine(cloneOfFooWithActivator == foo);
            Console.WriteLine(cloneOfFooWithActivator.GetType());

            Console.ReadLine();
        }

      

And the output is:

enter image description here

EDIT β†’ β†’ Based on your performance concerns I did some tests and also found another and better way to achieve what you want.

The best way to approach involves calling the MemberwiseClone method using a mixture of Reflection, Delegates and Jon Skeet . The idea is to convert the methodinfo instance to a delegate, more details can be found in this post by Jon Skeet.



Here's main ():

 static void Main(string[] args)
        {
            const int howManyTimes = 10000000;
            IPluginInterface foo = new Foo(true);
            foo.ShallowCloneWithDeepClonerLibrary(howManyTimes);
            foo.ShallowCloneWithReflection(howManyTimes);
            ((Foo)foo).ShallowCloneWithMemberWiseClone(howManyTimes);
            foo.ShallowCloneWithDelegatesAndReflection(howManyTimes);
            Console.ReadLine();
        }

      

As you can see, we are testing four shallow cloning approaches:

  • DeepCloner Library
  • Direct link to MemberWiseClone. (Not suitable for your case)
  • Using MethodInfo.Invoke where MethodInfo is MemberwiseClone
  • Using a delegate

This is the code for all 4 methods (which are extension methods):

public static void ShallowCloneWithDeepClonerLibrary(this object obj, int times)
    {
        Console.WriteLine($"Performing {times.ToString("##,###")} cloning operations with DeepCloner ShallowClone method:");
        var sw = new Stopwatch();
        sw.Start();
        for (var i = 0; i < times - 1; i++) obj.ShallowClone();
        var clone = obj.ShallowClone();
        sw.Stop();
        Console.WriteLine($"Total milliseconds elapsed: {sw.ElapsedMilliseconds}");
        Console.WriteLine($"Are both the same: {obj == clone}");
        Console.WriteLine($"Cloned object: {Environment.NewLine}{clone}{Environment.NewLine}");
    }

    public static void ShallowCloneWithMemberWiseClone(this Foo obj, int times)
    {
        Console.WriteLine($"Performing {times.ToString("##,###")} cloning operations wiht MemberwiseClone:");
        var sw = new Stopwatch();
        sw.Start();
        for (var i = 0; i < times - 1; i++) obj.Clone();
        var clone = obj.Clone();
        sw.Stop();
        Console.WriteLine($"Total milliseconds: {sw.ElapsedMilliseconds}");
        Console.WriteLine($"Are both the same: {obj == clone}");
        Console.WriteLine($"Cloned object: {Environment.NewLine}{clone}{Environment.NewLine}");
    }

    public static void ShallowCloneWithDelegatesAndReflection(this object obj, int times)
    {
        Console.WriteLine(
            $"Performing {times.ToString("##,###")} cloning operations by encapsulating MemberwiseClone method info in a delegate:");
        var sw = new Stopwatch();
        sw.Start();
        var type = obj.GetType();
        var clone = Activator.CreateInstance(type);
        var memberWiseClone = type.GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic);
        var memberWiseCloneDelegate =
            (Func<object, object>)Delegate.CreateDelegate(typeof(Func<object, object>), memberWiseClone);
        for (var i = 0; i < times; i++) clone = memberWiseCloneDelegate(obj);
        sw.Stop();
        Console.WriteLine($"Total milliseconds: {sw.ElapsedMilliseconds}");
        Console.WriteLine($"Are both the same: {obj == clone}");
        Console.WriteLine($"Cloned object: {Environment.NewLine}{clone}{Environment.NewLine}");
    }

    public static void ShallowCloneWithReflection(this object obj, int times)
    {
        Console.WriteLine($"Performing {times.ToString("##,###")} cloning operations manually with reflection and MemberwiseClone:");
        var sw = new Stopwatch();
        sw.Start();
        var type = obj.GetType();
        var memberWiseClone = type.GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic);
        var clone = Activator.CreateInstance(type);
        for (var i = 0; i < times - 1; i++)
            clone = memberWiseClone.Invoke(obj, null);
        sw.Stop();
        Console.WriteLine($"Total milliseconds: {sw.ElapsedMilliseconds}{Environment.NewLine}");
        Console.WriteLine($"Are both the same: {obj == clone}");
        Console.WriteLine($"Cloned object: {Environment.NewLine}{clone}{Environment.NewLine}");
    }

      

And the results in milliseconds for 10,000,000 cloning operations with each one:

  • DeepCloner: 791
  • MemberwiseClone directly: 463
  • MemberwiseClone with MethodInfo.Invoke: 2000
  • MemberwiseClone via delegate: 465

So we have a winner! Unfortunately, the winner is not a good fit for your case due to the implications of publishing the MemberwiseClone object in the class. But ... We have an amazing second place!

Here's the result:

enter image description here

+1


source







All Articles