如何在C#中复制一个类的实例

27
有没有办法在C#中复制一个对象?类似这样的操作:
var dupe = MyClass(original);

我希望它们是相等的,这样所有的数据成员都是相同的,但不共享同一内存位置。

2
问题不够清晰。您正在谈论对象而不是类,并且应该澄清您想要的语义,正如Groo所说的那样。https://dev59.com/r3VC5IYBdhLWcg3wykUt - Daniel Daranas
一种可能性是使用反射来创建一个通用方法,为您执行此操作:http://pmichaels.net/2016/03/04/dynamically-copy-a-class-from-one-instance-to-another-using-reflection/ - Paul Michaels
13个回答

42
你可能在谈论深拷贝(深拷贝 vs 浅拷贝)吧?
你可以选择以下几种方法之一:
1. 自己实现(硬编码)一个方法, 2. 尝试实现(或找到)一个使用反射或发射来动态执行的实现方法(在这里有详细解释), 3. 如果对象标记有 [Serializable] 属性,可以使用序列化和反序列化来创建深拷贝。
public static T DeepCopy<T>(T other)
{
    using (MemoryStream ms = new MemoryStream())
    {
        BinaryFormatter formatter = new BinaryFormatter();
        formatter.Serialize(ms, other);
        ms.Position = 0;
        return (T)formatter.Deserialize(ms);
    }
}

要获得一个浅拷贝,你可以使用Object.MemberwiseClone()方法,但这是一个受保护的方法,意味着你只能在类内部使用它。
在使用所有深拷贝方法时,重要的是要考虑到对其他对象的引用或循环引用,这可能导致创建比你想要的更深的拷贝。
⚠️ 安全警告:请阅读关于使用BinaryFormatter可能包含远程代码执行的危险性的警告...你可以使用提供链接中列出的首选替代方法。

序列化是一个不错的想法 :-)。 - Martin Hennings
1
@Martin:是的,那是最简单的方法,不过要使用本地的.NET序列化类,必须将类标记为[Serializable]。否则你就得使用其他实现方式。 - vgru
1
由于某些原因,上面的代码片段缺少了<T>。例如,我应该看到:public static T DeepCopy<T>(T other)。 - sapbucket

21

并非所有类都具有此功能。如果一个类具有这个功能,那么它可能会提供一个Clone方法。为了帮助实现自己的类的该方法,System.Object中定义了一个MemberwiseClone受保护方法,它可以对当前实例进行浅复制(即,字段被复制;如果它们是引用类型,则引用将指向原始位置)。


9
如果您的类只有属性,可以这样做:
SubCentreMessage actual;
actual = target.FindSubCentreFullDetails(120); //for Albany
SubCentreMessage s = new SubCentreMessage();

//initialising s with the same values as 
foreach (var property in actual.GetType().GetProperties())
{
    PropertyInfo propertyS = s.GetType().GetProperty(property.Name);
    var value = property.GetValue(actual, null);
    propertyS.SetValue(s, property.GetValue(actual, null), null);
}

如果您拥有字段和方法,我相信您可以使用反射在新类中重建它们。希望这可以帮到您。

6
是和不是。这是一个需要小心的领域,因为有一些陷阱(但也不难)。
首先,如果您在基类库(BCL)中稍微查看一下,您可能会发现ICloneable interface。并非所有类型都实现了此接口,但那些实现了此接口的类型具有Clone方法,该方法将返回相同类型的新实例,并(可能)具有相同的值。
然而,这里存在一个陷阱:ICloneable接口没有足够地指定预期进行深度克隆还是浅层克隆,因此某些实现会执行一项操作,而其他实现则执行另一项操作。因此,ICloneable并没有被广泛使用,其进一步使用正在积极地被反对-有关更多详细信息,请参见优秀的Framework Design Guidelines
如果您深入了解BCL,您可能会发现System.Object具有受保护的MemberwiseClone方法。虽然您无法直接从另一个类型调用此方法,但可以使用此方法在自己的对象中实现克隆。
一个常见的克隆模式是定义一个受保护的类构造函数,并将已存在的实例作为参数传递。像这样:
public class MyClass()
{
    public MyClass() {}

    protected MyClass(MyClass other)
    {
        // Cloning code goes here...
    }

    public MyClass Clone()
    {
        return new MyClass(this);
    }
}

然而,这显然只适用于您想要克隆的类型受您控制的情况。
如果您希望克隆一个无法修改且没有提供克隆方法的类型,则需要编写代码显式地从旧实例复制每个数据片段到新实例。

4

我认为作者在询问拷贝构造函数的问题...

答案是“可以,但只有在自己实现时”,没有一种“自动”的方式可以做到这一点,除非使用一些重型修补(即:反射):

class MyClass {
    private string _name;

    // standard constructor, so that we can make MyClass's without troubles
    public MyClass(string name) {_name = name}
    public MyClass(MyClass old) {
        _name = old._name;
        // etc...
    }
}

在这方面需要注意的另一件事是 IClonable 接口,以及它提供的 .Clone() 方法。同时,Object 类提供的 .MemberwiseClone() 受保护方法也可以帮助实现这两个方法。


3

使用JSON怎么样?

您可以从以下网址获取JSON软件包:https://www.nuget.org/packages/Newtonsoft.Json/

using Newtonsoft.Json;
public static List<T> CopyAll<T>(this List<T> list) {
  List<T> ret = new List<T>();
  string tmpStr = JsonConvert.SerializeObject(list);
  ret = JsonConvert.DeserializeObject<List<T>>(tmpStr);
  return ret;
}

谢谢@saud-khan,这帮助我实现了我想要的。 - MatVAD

2

这里有一些合理的解决方案,序列化是一种有效的方式,每个类也可以使用克隆方法。我写了一些代码并且在测试中似乎可行。

using System.Reflection; using System.Text.StringBuilder();

public static T CopyClass2<T>(T obj){
    T objcpy = (T)Activator.CreateInstance(typeof(T));
    obj.GetType().GetProperties().ToList()
    .ForEach( p => objcpy.GetType().GetProperty(p.Name).SetValue(objcpy, p.GetValue(obj)));
    return objcpy;

以下是更加详细的版本,上面的内容可能假设你理解Lambda表达式,这并不常见。如果你更注重可读性(完全有效),我建议使用下面的内容。

public static T CopyClass<T>(T obj)
{
    T objcpy = (T)Activator.CreateInstance(typeof(T));
    foreach (var prop in obj.GetType().GetProperties())
    {
        var value = prop.GetValue(obj);
        objcpy.GetType().GetProperty(prop.Name).SetValue(objcpy, value);
    }
    return objcpy;

使用此方法列出属性,以查看它是否被复制到新引用中。简单来说,它不会列出非基元(结构化类型)的属性,因此您将获得一个类名。

public static string ListAllProperties<T>(T obj)
{
        StringBuilder sb = new System.Text.StringBuilder();
        PropertyInfo[] propInfo = obj.GetType().GetProperties();
        foreach (var prop in propInfo)
        {
            var value = prop.GetValue(obj) ?? "(null)";
            sb.AppendLine(prop.Name + ": " + value.ToString());
        }
        return sb.ToString();
}

2
一种简单的克隆对象的方法是将其写入流中,然后重新读取它:
public object Clone()
{
    object clonedObject = null;
    BinaryFormatter formatter = new BinaryFormatter();
    using (Stream stream = new MemoryStream())
    {
        formatter.Serialize(stream, this);
        stream.Seek(0, SeekOrigin.Begin);
        clonedObject = formatter.Deserialize(stream);
    }

    return clonedObject;
}

但请注意,这可能会导致与所谓的聚合对象产生问题。

2
没有一种简单的方法可以始终奏效。如果您的类是[Serializable]或实现了ISerializable,则可以进行往返序列化以创建完全相同的副本。对于[DataContract]也是同样适用的。
如果您只想要一个浅表复制,可以尝试使用Object.MemberwiseClone()。不过这是一个受保护的方法,只能在类内部使用。
如果您很幸运,该类实现了ICloneable接口,那么您只需调用Clone()方法即可。

1
现在,使用 Json 序列化作为扩展方法可能是更好的解决方案,就像这样(也不需要用 [Serializable] 注释类):
public static class ExtensionMethods
{
    public static T DeepCopyV2<T>(this T source)
    {
        var jsonString = JsonConvert.SerializeObject(source);
        var newObject = JsonConvert.DeserializeObject<T>(jsonString);
    
        return newObject;
    }
}

并像这样使用它:

var newObj = obj.DeepCopyV2();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接