我想做一些事情,比如:

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

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

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

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


当前回答

编辑:项目已停止

如果您想真正克隆到未知类型,可以查看fastclone。

这是基于表达式的克隆,其工作速度比二进制序列化快10倍,并保持完整的对象图完整性。

这意味着:如果多次引用层次结构中的同一对象,克隆也将引用一个实例。

不需要对正在克隆的对象进行接口、属性或任何其他修改。

其他回答

C#9.0引入了需要记录的with关键字(谢谢Mark Nading)。这应该允许非常简单的对象克隆(如果需要,还可以进行突变),只需要很少的样板,但只需要一个记录。

您似乎无法通过将类放入通用记录来克隆(按值)类;

using System;
                
public class Program
{
    public class Example
    {
        public string A { get; set; }
    }
    
    public record ClonerRecord<T>(T a)
    {
    }

    public static void Main()
    {
        var foo = new Example {A = "Hello World"};
        var bar = (new ClonerRecord<Example>(foo) with {}).a;
        foo.A = "Goodbye World :(";
        Console.WriteLine(bar.A);
    }
}

这写着“再见世界:()”-字符串是通过引用复制的(不需要)。https://dotnetfiddle.net/w3IJgG

(令人难以置信的是,上面的方法可以正确地用于结构!https://dotnetfiddle.net/469NJv)

但克隆一条记录似乎确实可以按值进行缩进和克隆。

using System;

public class Program
{
    public record Example
    {
        public string A { get; set; }
    }
    
    public static void Main()
    {
        var foo = new Example {A = "Hello World"};
        var bar = foo with {};
        foo.A = "Goodbye World :(";
        Console.WriteLine(bar.A);
    }
}

这将返回“Hello World”,字符串是按值复制的!https://dotnetfiddle.net/MCHGEL

更多信息可以在博客文章中找到:

https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/with-expression

由于我在不同的项目中找不到满足我所有需求的克隆器,我创建了一个可以配置并适应不同代码结构的深度克隆器,而不是调整我的代码以满足克隆器的需求。这是通过向要克隆的代码添加注释来实现的,或者您只需保留代码的默认行为。它使用反射、类型缓存并基于fasterflect。对于大量数据和高对象层次结构(与其他基于反射/序列化的算法相比),克隆过程非常快。

https://github.com/kalisohn/CloneBehave

也可作为nuget包提供:https://www.nuget.org/packages/Clone.Behave/1.0.0

例如:以下代码将执行deepCloneAddress,但只执行_currentJob字段的浅拷贝。

public class Person 
{
  [DeepClone(DeepCloneBehavior.Shallow)]
  private Job _currentJob;      

  public string Name { get; set; }

  public Job CurrentJob 
  { 
    get{ return _currentJob; }
    set{ _currentJob = value; }
  }

  public Person Manager { get; set; }
}

public class Address 
{      
  public Person PersonLivingHere { get; set; }
}

Address adr = new Address();
adr.PersonLivingHere = new Person("John");
adr.PersonLivingHere.BestFriend = new Person("James");
adr.PersonLivingHere.CurrentJob = new Job("Programmer");

Address adrClone = adr.Clone();

//RESULT
adr.PersonLivingHere == adrClone.PersonLivingHere //false
adr.PersonLivingHere.Manager == adrClone.PersonLivingHere.Manager //false
adr.PersonLivingHere.CurrentJob == adrClone.PersonLivingHere.CurrentJob //true
adr.PersonLivingHere.CurrentJob.AnyProperty == adrClone.PersonLivingHere.CurrentJob.AnyProperty //true

我想要一个克隆器,用于非常简单的对象,主要是基元和列表。如果您的对象是开箱即用的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);
    }
}

当使用Marc Gravells protobuf net作为序列化程序时,接受的答案需要一些轻微的修改,因为要复制的对象不会被归因于[Serializable],因此不可序列化,Clone方法将抛出异常。我修改了它以与protobuf net一起使用:

public static T Clone<T>(this T source)
{
    if(Attribute.GetCustomAttribute(typeof(T), typeof(ProtoBuf.ProtoContractAttribute))
           == null)
    {
        throw new ArgumentException("Type has no ProtoContract!", "source");
    }

    if(Object.ReferenceEquals(source, null))
    {
        return default(T);
    }

    IFormatter formatter = ProtoBuf.Serializer.CreateFormatter<T>();
    using (Stream stream = new MemoryStream())
    {
        formatter.Serialize(stream, source);
        stream.Seek(0, SeekOrigin.Begin);
        return (T)formatter.Deserialize(stream);
    }
}

这将检查[ProtoControl]属性的存在,并使用protobufs自己的格式化程序来序列化对象。

好的,这篇文章中有一些明显的反射示例,但反射通常很慢,直到你开始正确缓存它。

如果你能正确缓存它,那么它将以4.6s的速度深度克隆1000000个对象(由Watcher测量)。

static readonly Dictionary<Type, PropertyInfo[]> ProperyList = new Dictionary<Type, PropertyInfo[]>();

而不是获取缓存的财产或将新属性添加到字典并简单使用它们

foreach (var prop in propList)
{
        var value = prop.GetValue(source, null);   
        prop.SetValue(copyInstance, value, null);
}

在另一个答案中查看我的帖子中的完整代码

https://stackoverflow.com/a/34365709/4711853