我想做一些事情,比如:

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

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

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

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


当前回答

我更喜欢复制构造函数而不是克隆。意图更加明确。

其他回答

使用System.Text.Json:

https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-apis/

public static T DeepCopy<T>(this T source)
{
    return source == null ? default : JsonSerializer.Parse<T>(JsonSerializer.ToString(source));
}

新的API使用Span<T>。这应该很快,最好做一些基准测试。

注意:不需要像Json.NET那样使用ObjectCreationHandling。Replace,因为默认情况下它将替换集合值。您现在应该忘记Json.NET,因为所有的东西都将被新的官方API所取代。

我不确定这是否适用于私人领域。

基本上,您需要实现ICloneable接口,然后实现对象结构复制。如果它是所有成员的深度拷贝,您需要确保(与您选择的解决方案无关)所有子级都是可克隆的。有时,在这个过程中,您需要注意一些限制,例如,如果您复制ORM对象,大多数框架只允许一个对象附加到会话,并且您不能克隆该对象,或者如果可能,您需要关注这些对象的会话附加。

干杯

我更喜欢复制构造函数而不是克隆。意图更加明确。

深度克隆就是复制状态。对于.net state,表示字段。

假设有一个层次结构:

static class RandomHelper
{
    private static readonly Random random = new Random();

    public static int Next(int maxValue) => random.Next(maxValue);
}

class A
{
    private readonly int random = RandomHelper.Next(100);

    public override string ToString() => $"{typeof(A).Name}.{nameof(random)} = {random}";
}

class B : A
{
    private readonly int random = RandomHelper.Next(100);

    public override string ToString() => $"{typeof(B).Name}.{nameof(random)} = {random} {base.ToString()}";
}

class C : B
{
    private readonly int random = RandomHelper.Next(100);

    public override string ToString() => $"{typeof(C).Name}.{nameof(random)} = {random} {base.ToString()}";
}

克隆可以通过以下方式完成:

static class DeepCloneExtension
{
    // consider instance fields, both public and non-public
    private static readonly BindingFlags bindingFlags =
        BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;

    public static T DeepClone<T>(this T obj) where T : new()
    {
        var type = obj.GetType();
        var result = (T)Activator.CreateInstance(type);

        do
            // copy all fields
            foreach (var field in type.GetFields(bindingFlags))
                field.SetValue(result, field.GetValue(obj));
        // for every level of hierarchy
        while ((type = type.BaseType) != typeof(object));

        return result;
    }
}

演示1:

Console.WriteLine(new C());
Console.WriteLine(new C());

var c = new C();
Console.WriteLine($"{Environment.NewLine}Image: {c}{Environment.NewLine}");

Console.WriteLine(new C());
Console.WriteLine(new C());

Console.WriteLine($"{Environment.NewLine}Clone: {c.DeepClone()}{Environment.NewLine}");

Console.WriteLine(new C());
Console.WriteLine(new C());

结果:

C.random = 92 B.random = 66 A.random = 71
C.random = 36 B.random = 64 A.random = 17

Image: C.random = 96 B.random = 18 A.random = 46

C.random = 60 B.random = 7 A.random = 37
C.random = 78 B.random = 11 A.random = 18

Clone: C.random = 96 B.random = 18 A.random = 46

C.random = 33 B.random = 63 A.random = 38
C.random = 4 B.random = 5 A.random = 79

注意,所有新对象的随机字段都有随机值,但克隆与图像完全匹配

演示2:

class D
{
    public event EventHandler Event;
    public void RaiseEvent() => Event?.Invoke(this, EventArgs.Empty);
}

// ...

var image = new D();
Console.WriteLine($"Created obj #{image.GetHashCode()}");

image.Event += (sender, e) => Console.WriteLine($"Event from obj #{sender.GetHashCode()}");
Console.WriteLine($"Subscribed to event of obj #{image.GetHashCode()}");

image.RaiseEvent();
image.RaiseEvent();

var clone = image.DeepClone();
Console.WriteLine($"obj #{image.GetHashCode()} cloned to obj #{clone.GetHashCode()}");

clone.RaiseEvent();
image.RaiseEvent();

结果:

Created obj #46104728
Subscribed to event of obj #46104728
Event from obj #46104728
Event from obj #46104728
obj #46104728 cloned to obj #12289376
Event from obj #12289376
Event from obj #46104728

注意,事件支持字段也被复制,客户端也订阅了克隆的事件。

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

使用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