c# - 克隆一个对象,同时从扩展方法维护派生类型

标签 c# clone

不确定我的白话是否正确,请耐心等待。

我在插件中使用了一个公共(public)接口(interface),我希望实现此接口(interface)的类可以克隆,同时保持它们的声明类型和其中的所有属性。

我知道 ICloneable 并且我可以简单地将其实现到我的界面中,但我不想将实现该界面的要求传递给插件开发人员并希望自己控制它。

另外值得注意的是,它需要轻巧,不需要很深。除了实现我的插件接口(interface)外,我在设计时也不知道声明类型的任何内容,因此我需要将其转换为“未知”源类型。

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;}
}

然后我需要从我的应用程序中调用类似这样的东西:

// 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();

我试过了 this ,但这需要我在设计时知道类型。

最佳答案

我推荐你使用DeepCloner ,可从 NuGet 获得。我认为这个库恰好实现了您所需要的,并且作为扩展方法。它也是开源的并托管在 GitHub 中,因此如果您想添加更多功能或只是想了解它的工作原理,您可以查看代码。

来自项目网站:

Also, there is no requirement to specify object type for cloning. Object can be casted to inteface or as an abstract object, you can clone array of ints as abstract Array or IEnumerable, even null can be cloned without any errors.

我做了这个示例来展示它是如何工作的:

一个接口(interface)和一个实现这个接口(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}";
    }

然后在 main 中添加 using Force.DeepCloner; 和...

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();
        }

输出:

enter image description here

EDIT>>>> 基于您对性能的担忧,我进行了一些测试,并找到了另一种更好的方法来实现您想要的。

更好的方法是调用 MemberwiseClone 方法,同时使用 Reflection、Delegates 和 Jon Skeet。这个想法是将 methodinfo 实例转换为委托(delegate),更多信息可以在 this 中找到。乔恩·斯基特 (Jon Skeet) 发表。

这是 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();
        }

正如您可能注意到的,我们正在测试四种浅克隆方法:

  • DeepCloner 库
  • 直接公开 MemberWiseClone。 (不适合你的情况)
  • 使用 MethodInfo.Invoke,其中 MethodInfo 是 MemberwiseClone
  • 使用委托(delegate)

这是所有 4 个方法的代码(它们是扩展方法):

public static void ShallowCloneWithDeepClonerLibrary(this object obj, int times)
    {
        Console.WriteLine($"Performing {times.ToString("##,###")} cloning operations with DeepCloner's 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}");
    }

以及每次进行 10,000,000 次克隆操作的结果(以毫秒为单位):

  • DeepCloner:791
  • MemberwiseClone 直接:463
  • MemberwiseClone with MethodInfo.Invoke:2000
  • MemberwiseClone 通过委托(delegate):465

所以,我们有一个赢家!不幸的是,获胜者不适合您的情况,因为这意味着在类里面公开 MemberwiseClone。但是......我们有一个巨大的第二名!

这是输出:

enter image description here

关于c# - 克隆一个对象,同时从扩展方法维护派生类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44847976/

相关文章:

java - 如何克隆 ArrayList 并克隆其内容?

c# - 让 Microsoft.jet.oledb.4.0 或 microsoft.ace.oledb.12.0 在 64 位 Windows 7 计算机上运行

c# - .NET 中的 System.nanoTime() 等效于什么?

c# - 什么是等效于 modelBuilder 的 HasDefaultValueSql 的 Entity Framework Core 属性?

ubuntu - 使用 grafonnet-lib 运行 jsonnet 时没有此类文件或目录错误

克隆指向 int 的 void* 指针

c# - 如何克隆 HttpWebRequest?

c# - 使用 CoCreateInstance 从 C++ 调用 C# dll

c# - WCF 数据服务 : How to query ALL entities when Paging is enabled?

oracle - PL/SQL : Any trick to avoid cloning of objects?