如何在不将类标记为Serializable的情况下进行深复制

19

考虑以下类:

class A
{
    public List<B> ListB;

    // etc...
}

B是另一个可能继承/包含其他类的类。


在这种情况下:

  1. A是一个大类,包含很多引用类型
  2. 我无法将B标记为[Serializable],因为我没有访问B的源代码

下面的方法不能执行深拷贝:

  1. 我不能使用ICloneableMemberwiseClone,因为类A包含许多引用类型
  2. 我不能编写A的复制构造函数,因为该类很大,不断地增加,并且包含无法进行深度复制的类(如B
  3. 我不能使用序列化,因为我无法将所包含的类(例如B,无法获取源代码)标记为[Serializable]

如何深度复制类A


@Will:我很同情,我也试图重新格式化,真是一团糟!<g> - lexu
谢谢,其实我是这个网站的新手,我在打字的时候进行了格式化,但当我发布后就出现了这种情况。 - Gaddigesh
请查看此答案:https://dev59.com/QF_Va4cB1Zd3GeqPTnug#52097307,了解有关无需序列化克隆对象的信息。 - Ahmed Sabry
7个回答

11

我已经不再使用序列化来进行深度复制,因为它没有足够的控制力(并非每个类都需要以相同的方式进行复制)。然后我开始实现自己的深度复制接口,并按照应该复制的方式复制每个属性。

常见的拷贝引用类型的方法:

  • 使用拷贝构造函数
  • 使用工厂方法(例如:不可变类型)
  • 使用自己的“克隆”方法
  • 仅拷贝引用(例如:其他根类型)
  • 创建新实例并复制属性(例如:由你自己编写的类型缺少拷贝构造函数)

示例:

class A
{
  // copy constructor
  public A(A copy) {}
}

// a referenced class implementing 
class B : IDeepCopy
{
  object Copy() { return new B(); }
}

class C : IDeepCopy
{
  A A;
  B B;
  object Copy()
  {
    C copy = new C();

    // copy property by property in a appropriate way
    copy.A = new A(this.A);
    copy.B = this.B.Copy();
  }
}

你可能会认为这是一项巨大的工作量。但最终,它是简单和直接的,可以在需要时进行调整,并且完全符合你的需求。


如果你有一个大对象怎么办?请不要告诉我拥有一个大对象就是问题,因为那只会创造出更大的问题。 - Fabio Milheiro
@Bomboca;:当然,你需要在某个地方编写显式深拷贝的代码。但是没有其他干净的解决方案。如果有另一种通用解决方案,它要么非常不稳定,要么需要大量的配置工作,这使得它不够透明且更难控制。那么,仅仅复制值,以你想要的方式进行复制,有什么问题呢? - Stefan Steinegger
我很欣赏你的观点,但设置大对象的副本太麻烦了。然后有人添加一个新字段,忘记或甚至不知道这个复制的事情。然后 QA 就会说它不起作用。当然,我们会解决的,但通用地完成可以确保更具未来性。为了抽象化低级操作以便我们可以有效地解决问题,透明度较低是付出的代价,但这是必要的。尽管如此,对于非常小的类,我仍然会接受您的解决方案。 - Fabio Milheiro
1
@Bomboca:这不是针对小类的解决方案,而是针对复杂类的。正如我所指出的:有些属性需要深拷贝,其他引用拷贝,或者可能需要非常特定的内容。例如,如果您想创建订单项的副本,您不希望订单或目录中的文章被复制,因为它们是引用。大类应该分成较小的类来管理。通用解决方案适用于可以有许多属性但所有属性都是简单类型的微不足道的类。 - Stefan Steinegger
我宁愿有一些规则,让它在未来能够正常工作,而不是因为人类不知道或忘记了某些事情而导致映射失败。如果不考虑序列化,那么反射肯定应该被考虑。在我的情况下,序列化已经足够好了,但我相信有一个NuGet包可以做到这一点,避免所有繁琐和容易出错的手动工作。不过我理解你的观点。 - Fabio Milheiro

3
你可以试试这个。它对我有用。
    public static object DeepCopy(object obj)
    {
        if (obj == null)
            return null;
        Type type = obj.GetType();

        if (type.IsValueType || type == typeof(string))
        {
            return obj;
        }
        else if (type.IsArray)
        {
            Type elementType = Type.GetType(
                 type.FullName.Replace("[]", string.Empty));
            var array = obj as Array;
            Array copied = Array.CreateInstance(elementType, array.Length);
            for (int i = 0; i < array.Length; i++)
            {
                copied.SetValue(DeepCopy(array.GetValue(i)), i);
            }
            return Convert.ChangeType(copied, obj.GetType());
        }
        else if (type.IsClass)
        {

            object toret = Activator.CreateInstance(obj.GetType());
            FieldInfo[] fields = type.GetFields(BindingFlags.Public |
                        BindingFlags.NonPublic | BindingFlags.Instance);
            foreach (FieldInfo field in fields)
            {
                object fieldValue = field.GetValue(obj);
                if (fieldValue == null)
                    continue;
                field.SetValue(toret, DeepCopy(fieldValue));
            }
            return toret;
        }
        else
            throw new ArgumentException("Unknown type");
    }

感谢DetoX83在code project上发布的文章,介绍了关于C#对象深拷贝的方法。


应该将代码行 Type elementType = Type.GetType(type.FullName.Replace("[]", string.Empty)); 替换为 Type elementType = type.GetElementType(); - Karrok

0

尝试使用内存流来获取对象的深拷贝:

 public static T MyDeepCopy<T>(this T source)
            {
                try
                {

                    //Throw if passed object has nothing
                    if (source == null) { throw new Exception("Null Object cannot be cloned"); }

                    // Don't serialize a null object, simply return the default for that object
                    if (Object.ReferenceEquals(source, null))
                    {
                        return default(T);
                    }

                    //variable declaration
                    T copy;
                    var obj = new DataContractSerializer(typeof(T));
                    using (var memStream = new MemoryStream())
                    {
                        obj.WriteObject(memStream, source);
                        memStream.Seek(0, SeekOrigin.Begin);
                        copy = (T)obj.ReadObject(memStream);
                    }
                    return copy;
                }
                catch (Exception)
                {
                    throw;
                }
            }

这里有更多信息。


3
如何在不将类标记为Serializable的情况下进行深拷贝。若要使用MemoryStream对类进行序列化,您的类需要是可序列化的。 - Andy_Vulhop
我尝试了几个(不可序列化的)类,还有我自己的简单示例类。在所有情况下都不需要“Serializable”子句。但是,我承认,除非您的序列化类标记为'DataContractAttribute',否则会出现异常。并且您想要序列化的所有成员都必须标记为'DataMemberAttribute'。如果该类是集合,则应将其标记为'CollectionDataContractAttribute'。实际上,您会收到声称如此的异常消息。我认为这是公平的。 - Sold Out
是的,将原始类标记为任何东西 - 会让它重新开始.. :o/ - Sold Out

0

1
@m_oLogin:OP希望它不被标记为“Serializable”。 - KMån
据我所知,作者表示他无法将内部类标记为[Serializable],因为他无法访问源代码...但他仍然可以将内部的“ListB”成员标记为[NonSerialized],然后使用序列化进行深层复制。 - Gad

0

你的接口IDeepCopy正是ICloneable所指定的。

class B : ICloneable
{
     public object Clone() { return new B(); }
}

并且实现更为友好:

class B : ICloneable
{
     public B Clone() { return new B(); }
     // explicit implementation of ICloneable
     object ICloneable.Clone() { return this.Clone(); }
}

5
ICloneable并不意味着它是一个深度复制。在.NET中,有些类被标记为ICloneable但并不进行深度复制。 - Amir

0
    private interface IDeepCopy<T> where T : class
    {
        T DeepCopy();
    }

    private class MyClass : IDeepCopy<MyClass>
    {
        public MyClass DeepCopy()
        {
            return (MyClass)this.MemberwiseClone();
        }
    }

优点:您可以控制复制过程(如果您的类具有标识符属性,则可以设置它们,或者您可以编写其他业务逻辑代码)


缺点:类可以被标记为密封的



7
MemberwiseClone()如何成为深拷贝?这是示例代码吗?如果是,为什么没有标记?如果已经有好的示例代码回答了,为什么要提供未标记的坏示例代码?到底是谁点赞了这个东西? - No answer

-1

我在另一个线程中看到的答案,使用JSON序列化是我见过的最好的。

public static T CloneJson<T>(this T source)
{      
    if (Object.ReferenceEquals(source, null))
    {
        return default(T);
    }    
    return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source));
}

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