🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# 原则32:避免 ICloneable **By D.S.Qiu** **尊重他人的劳动,支持原创,转载请注明出处:[http://dsqiu.iteye.com](http://dsqiu.iteye.com)** IConeable 听起来是一个很不错的想法:你类型实现了 IConeable 接口然后就支持复制。如果你不想要支持复制,你就不需要实现它。但是你的类型不能在真空中存在。你支持 IConeable 的决定会影响它的子类。一旦一个类型支持 ICloneable ,它的所有子类也都必须支持 ICloneable 。它的所有成员的类型都必须支持 ICloneable 或者有其他机制去复制。最后,如果支持深度复制并且当你类型包含 web 对象就会很有问题。 ICloneable 的官方定义就给出了这个问题:它支持深复杂或浅复制。浅复制创建一个新对象包含所有成员变量的复制。如果这些成员变量是引用类型的,新对象引用和原来对象是同一个对象。深复制创建新对象同样包含所有成员变量的复制。所有的引用类型都会被嵌套复制。对于内置变量,例如整数,深复制和浅复制产生的结果是一样的。类型支持哪一个?这就依赖于具体的类型。但是在同一个对象混合深复制和浅复制会引起相当不一致的表现。当你去趟了 ICloneable 的浑水,这就再说难免了。大多数情况下,避免使用 ICloneable 使得类更简单。它很容易使用,并且很容易实现。 任何只包含内置类的成员变量的值类型不需要支持 ICloneable ;简单的赋值复制 struct 的所有值比 Clone() 更高效。Clone() 会对返回值进行封箱,以至于强制转换为 System.Object 的引用。调用者必须进行强制类型转换才能从箱中提取值。这样做已经够了。不要重写 Clone() 函数来进行赋值复制。 如果值类型包含引用类型会怎么样?最常见的例子是值类型包含一个 string : ``` public struct ErrorMessage { private int errCode; private int details; private string msg; // details elided } ``` string 是一个特殊例子因为它是不可变的类。如果你赋值一个 ErrorMessage 对象,两个 ErrorMessage 对象会引用相同一个字符串。它不会引起一般引用类型的可能会出现的错误。如果你通过任何一个引用改变 msg 变量,会创建一个 string 对象(查看原则16)。 普遍的例子是创建一个包含任意引用变量的 struct 会更复杂。这也很少见。struct 内置的赋值创建浅复制,两个 struct 会引用相同的对象。为了创建深复制,你需要克隆包含的引用类型对象,而且你需要知道这个引用类型通过 Clone() 方法来支持深复制。这样,要做的工作如果包含的引用类型支持 ICloneable ,并且它的 Clone() 方法创建深复制。 下面我们开始讨论引用类型。引用类型支持 ICloneable 接口说明它们支持浅复制或深复制。你应该谨慎支持 ICloneable 因为这样就必须让这个类的所有子类也支持 ICloneable 。考虑下面的简单的继承结构: ``` class BaseType : ICloneable { private string label = "class name"; private int[] values = new int[10]; public object Clone() { BaseType rVal = new BaseType(); rVal.label = label; for (int i = 0; i < values.Length; i++) rVal.values[i] = values[i]; return rVal; } } class Derived : BaseType { private double[] dValues = new double[10]; static void Main(string[] args) { Derived d = new Derived(); Derived d2 = d.Clone() as Derived; if (d2 == null) Console.WriteLine("null"); } } ``` 如果你运行这个程序,你会发现 d2 的值是 null 。Derived 类从基类 BaseType 继承 ICloneable.Clone() ,但是实现却对子类是不正确的:它只是克隆基类 BaseType.Clone() 创建基类对象,而不是子类对象。这就是测试程序中为什么 d2 为 null —— 它不是 Derived 对象。然而,即使你克服了这个问题, BaseType.Clone() 不能复制定义在 Derived 的 dValues 数组。所以当你实现 ICloneable ,你必须强制所有子类也都实现。实际上,你可以提供一个钩子函数让所有子类能有自己的实现(查看原则23)。为了支持克隆,子类只能添加实现了 ICloneable 的值类型或引用类型的成员变量。这是对于子类是非常严格的限制。在基类支持 ICloneable 增加了子类的负担,所以你应该在非封闭的类避免实现 ICloneable 。 如果整个类的继承结构都必须实现 ICloneable ,你可以差un感觉一个 abstract Clone() 方法,强制子类实现它。在这些例子,你还需要定义子类复制基类成员的方法。可以定义一个 protected 的复制构造函数: ``` class BaseType { private string label; private int[] values; protected BaseType() { label = "class name"; values = new int[10]; } // Used by devived values to clone protected BaseType(BaseType right) { label = right.label; values = right.values.Clone() as int[]; } } sealed class Derived : BaseType, ICloneable { private double[] dValues = new double[10]; public Derived() { dValues = new double[10]; } // Construct a copy // using the base class copy ctor private Derived(Derived right) : base(right) { dValues = right.dValues.Clone() as double[]; } public object Clone() { Derived rVal = new Derived(this); return rVal; } } ``` 基类没有实现 ICloneable ;提供了 protected 的复制构造函数,让子类能拿复制基类的部分。叶节点的类都是封闭的,当有必要的时候实现 ICloneable 。基类不会强制所有子类实现 ICloneable ,但是必须提供子类因支持 ICloneable 而需要的方法。 ICloneable 仍有它的用处,但是这是一个例外而不是指导规则。 .NET 框架更新支持泛型时,而没有添加 ICloneable&lt;T&gt; 的支持是非常有意义的。你不应该对值类型添加 ICloneable 的支持;而是使用赋值操作。当复制操作对叶借点封闭类很重要,你就应该添加 ICloneable 支持。当基类支持 ICloneable 你就为此创建 protected 复制够函数。对于其他的所有情况,避免使用 ICloneable 。 小结: 这个原则其实强调的重点是不管是值类型还是引用类型如果实现了 ICloneable 接口,这个类的成员变量和继承结构也要实现 ICloneable ,才能做到深复制和浅复制的一致性。这点其实跟 Java 是一样的! 欢迎各种不爽,各种喷,写这个纯属个人爱好,秉持”分享“之德! 有关本书的其他章节翻译请[点击查看](/category/297763),转载请注明出处,尊重原创! 如果您对D.S.Qiu有任何建议或意见可以在文章后面评论,或者发邮件(gd.s.qiu@gmail.com)交流,您的鼓励和支持是我前进的动力,希望能有更多更好的分享。 转载请在**文首**注明出处:[http://dsqiu.iteye.com/blog/2087490](/blog/2087490) 更多精彩请关注D.S.Qiu的博客和微博(ID:静水逐风)