我想做一些事情,比如:

MyObject myObj = GetMyObj(); // Create and fill a new object
MyObject newObj = myObj.Clone();

然后对未反映在原始对象中的新对象进行更改。

我通常不需要这个功能,所以当有必要时,我会创建一个新对象,然后单独复制每个属性,但这总是让我觉得有更好或更优雅的方法来处理这种情况。

如何克隆或深度复制对象,以便在原始对象中不反映任何更改的情况下修改克隆的对象?


当前回答

遵循以下步骤:

使用返回T的只读Self属性和ICloneable<out T>定义ISelf<T>,ICloneale<out T<派生自ISelf>并包含方法T Clone()。然后定义一个CloneBase类型,该类型实现了一个受保护的虚拟泛型VirtualClone,将MemberwiseClone转换为传入的类型。每个派生类型都应该通过调用基本克隆方法来实现VirtualClone,然后执行所需的操作来正确克隆父VirtualClone方法尚未处理的派生类型的那些方面。

为了实现最大的继承通用性,公开公共克隆功能的类应该是密封的,但派生自一个基类,该基类在其他方面是相同的,除非缺少克隆。不要传递显式可克隆类型的变量,而是采用ICloneable<theNonCloneableType>类型的参数。这将允许期望Foo的可克隆衍生物与DerivedFoo的可克隆衍生物一起工作的例程,但也允许创建Foo的不可克隆衍生物。

其他回答

找到了这个包,与之相比,DeepCloner似乎更快,而且没有依赖关系。

https://github.com/AlenToma/FastDeepCloner

我对当前的答案做了一些基准测试,发现了一些有趣的事实。

使用BinarySerializer=>https://stackoverflow.com/a/78612/6338072

使用XmlSerializer=>https://stackoverflow.com/a/50150204/6338072

使用Activator.CreateInstance=>https://stackoverflow.com/a/56691124/6338072

这些是结果

BenchmarkDotNet=v0.13.1, OS=Windows 10.0.18363.1734 (1909/November2019Update/19H2)

Intel Core i5-6200U CPU 2.30GHz(Skylake),1个CPU,4个逻辑核和2个物理核[主机]:.NET Framework 4.8(4.8.4400.0),X86 LegacyJIT默认作业:.NET Framework 4.8(4.8.4400.0),X86 LegacyJIT

Method Mean Error StdDev Gen 0 Allocated
BinarySerializer 220.69 us 4.374 us 9.963 us 49.8047 77 KB
XmlSerializer 182.72 us 3.619 us 9.405 us 21.9727 34 KB
Activator.CreateInstance 49.99 us 0.992 us 2.861 us 1.9531 3 KB

遵循以下步骤:

使用返回T的只读Self属性和ICloneable<out T>定义ISelf<T>,ICloneale<out T<派生自ISelf>并包含方法T Clone()。然后定义一个CloneBase类型,该类型实现了一个受保护的虚拟泛型VirtualClone,将MemberwiseClone转换为传入的类型。每个派生类型都应该通过调用基本克隆方法来实现VirtualClone,然后执行所需的操作来正确克隆父VirtualClone方法尚未处理的派生类型的那些方面。

为了实现最大的继承通用性,公开公共克隆功能的类应该是密封的,但派生自一个基类,该基类在其他方面是相同的,除非缺少克隆。不要传递显式可克隆类型的变量,而是采用ICloneable<theNonCloneableType>类型的参数。这将允许期望Foo的可克隆衍生物与DerivedFoo的可克隆衍生物一起工作的例程,但也允许创建Foo的不可克隆衍生物。

我想要一个克隆器,用于非常简单的对象,主要是基元和列表。如果您的对象是开箱即用的JSON可串行化的,那么这个方法就可以了。这不需要修改或实现克隆类上的接口,只需要一个JSON序列化程序,如JSON.NET。

public static T Clone<T>(T source)
{
    var serialized = JsonConvert.SerializeObject(source);
    return JsonConvert.DeserializeObject<T>(serialized);
}

此外,您可以使用此扩展方法

public static class SystemExtension
{
    public static T Clone<T>(this T source)
    {
        var serialized = JsonConvert.SerializeObject(source);
        return JsonConvert.DeserializeObject<T>(serialized);
    }
}

我找到了一种新的方法,那就是Emit。

我们可以使用Emit将IL添加到应用程序并运行它。但我认为这不是一个好方法,因为我想完善这一点,所以我写了我的答案。

Emit可以看到官方文件和指南

你应该学习一些IL来阅读代码。我将编写可以在类中复制属性的代码。

public static class Clone
{        
    // ReSharper disable once InconsistentNaming
    public static void CloneObjectWithIL<T>(T source, T los)
    {
        //see http://lindexi.oschina.io/lindexi/post/C-%E4%BD%BF%E7%94%A8Emit%E6%B7%B1%E5%85%8B%E9%9A%86/
        if (CachedIl.ContainsKey(typeof(T)))
        {
            ((Action<T, T>) CachedIl[typeof(T)])(source, los);
            return;
        }
        var dynamicMethod = new DynamicMethod("Clone", null, new[] { typeof(T), typeof(T) });
        ILGenerator generator = dynamicMethod.GetILGenerator();

        foreach (var temp in typeof(T).GetProperties().Where(temp => temp.CanRead && temp.CanWrite))
        {
            //do not copy static that will except
            if (temp.GetAccessors(true)[0].IsStatic)
            {
                continue;
            }

            generator.Emit(OpCodes.Ldarg_1);// los
            generator.Emit(OpCodes.Ldarg_0);// s
            generator.Emit(OpCodes.Callvirt, temp.GetMethod);
            generator.Emit(OpCodes.Callvirt, temp.SetMethod);
        }
        generator.Emit(OpCodes.Ret);
        var clone = (Action<T, T>) dynamicMethod.CreateDelegate(typeof(Action<T, T>));
        CachedIl[typeof(T)] = clone;
        clone(source, los);
    }

    private static Dictionary<Type, Delegate> CachedIl { set; get; } = new Dictionary<Type, Delegate>();
}

代码可以是深度复制,但它可以复制属性。如果你想让它成为深度副本,你可以改变它,因为IL太难了,我做不到。