Item 27: Avoid ICloneable
避免ICloneable
ICloneable sounds like a good idea: You implement the ICloneable interface for types that support copies. If you don't want to support copies, don't implement it. But your type does not live in a vacuum. Your decision to support ICloneable affects derived types as well. Once a type supports ICloneable, all its derived types must do the same. All its member types must also support ICloneable or have some other mechanism to create a copy. Finally, supporting deep copies is very problematic when you create designs that contain webs of objects. ICloneable finesses this problem in its official definition: It supports either a deep or a shallow copy. A shallow copy creates a new object that contains copies of all member variables. If those member variables are reference types, the new object refers to the same object that the original does. A deep copy creates a new object that copies all member variables as well. All reference types are cloned recursively in the copy. In built-in types, such as integers, the deep and shallow copies produce the same results. Which one does a type support? That depends on the type. But mixing shallow and deep copies in the same object causes quite a few inconsistencies. When you go wading into the ICloneable waters, it can be hard to escape. Most often, avoiding ICloneable altogether makes a simpler class. It's easier to use, and it's easier to implement.
ICloneable听起来是个好主意:为支持拷贝的类型实现ICloneable接口。如果你不想支持拷贝,就不用实现。但是你的类型不是生活在真空里,你的支持ICloneable的决定也影响到派生类。一旦一个类型支持ICloneable,所有它的派生类都要支持,所有成员的类型也都要支持ICloneable或者可以有其它机制可以制造拷贝。最后,当你创建包含有页面对象的设计时,要支持深拷贝有很多问题要解决。ICloneable在它的官方定义上巧妙的绕过了这个问题:它支持深拷贝或者浅拷贝。浅拷贝,创建一个新的对象,包含所有成员变量的副本,如果这些成员变量是引用类型,新对象和原来的对象指向同一个对象。深拷贝,创建一个新的对象,也拷贝所有的成员变量,所有的引用类型在副本里被递归的进行克隆。在内建的类型里,像整型,深拷贝和浅拷贝产生同样的结果。一个类型到底支持哪一个呢?取决于类型本身。但是在同一个对象上,混淆了浅拷贝和深拷贝,将引起很多不一致性。一旦蹚了ICloneable的浑水,就很难逃出来了。通常,避免ICloneable可以得到简单的类,这样容易使用也容易实现。
Any value type that contains only built-in types as members does not need to support ICloneable; a simple assignment copies all the values of the struct more efficiently than Clone(). Clone() must box its return so that it can be coerced into a System.Object reference. The caller must perform another cast to extract the value from the box. You've got enough to do. Don't write a Clone() function that replicates assignment.
任何仅仅包含有内建类型成员的类型,不需要支持ICloneable;一个简单的赋值将拷贝这个结构体的所有值,比Clone()更高效。Clone()必须将它的返回值进行装箱,那样的话,才能够被强制为System.Object引用。调用者必须执行另一次强制转换从箱子里提取该值。你有足够的事情去做,不要编写Clone()方法来重复赋值。
What about value types that contain reference types? The most obvious case is a value type that contains a string:
包含有引用类型的值类型会是什么情况呢?最明显的情况就是,包含字符串的值类型。
- public struct ErrorMessage
- {
- private int errCode;
- private int details;
- private string msg;
- // details elided
- }
string is a special case because this class is immutable. If you assign an error message object, both error message objects refer to the same string. This does not cause any of the problems that might happen with a general reference type. If you change the msg variable through either reference, you create a new string object (see Item 7).
String是一个特殊的情况,因为该类是不可变的。如果你向一个错误消息对象进行赋值,两个错误消息对象将指向同一个字符串。这不会引起那些在一般的引用类型上会发生的问题。如果你通过其它引用改变msg变量,就创建了一个新的字符串对象(见Item7)
The general case of creating a struct that contains arbitrary reference variables is more complicated. It's also far more rare. The built-in assignment for the struct creates a shallow copy, with both structs referring to the same object. To create a deep copy, you need to clone the contained reference type, and you need to know that the reference type supported a deep copy with its Clone() method. In either way, you don't add support for ICloneable to a value type; the assignment operator creates a new copy of any value type.
创建结构体的一般情况(包含任意引用变量)更复杂,也更少见。结构体的内建的赋值操作,会创建一个浅拷贝,两个结构体都指向同一个对象。为了创建一个深拷贝,你需要克隆包含的引用类型,需要知道该引用类型使用Clone()方法支持深拷贝。从另一方面讲,你不需要为值类型添加ICloneable支持,赋值操作符为任何值类型创建一个新的拷贝。
That covers value types: There is never a good reason to support the ICloneable interface in value types. Now let's move on to reference types. Reference types should support the ICloneable interface to indicate that they support either shallow or deep copying. You should add support for ICloneable judiciously because doing so mandates that all classes derived from your type must also support ICloneable. Consider this small hierarchy:
这句话涵盖了值类型:没有任何好的理由要求值类型支持ICloneable接口。现在让我们看引用类型。引用类型应该支持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");
- }
- }
If you run this program, you will find that the value of d2 is null. The Derived class does inherit ICloneable.Clone() from BaseType, but that implementation is not correct for the Derived type: It only clones the base type. BaseType.Clone() creates a BaseType object, not a Derived object. That is why d2 is null in the test program it's not a Derived object. However, even if you could overcome this problem, BaseType.Clone() could not properly copy the _dValues array that was defined in Derived. When you implement ICloneable, you force all derived classes to implement it as well. In fact, you should provide a hook function to let all derived classes use your implementation (see Item 21). To support cloning, derived classes can add only member variables that are value types or reference types that implement ICloneable. That is a very stringent limitation on all derived classes. Adding ICloneable support to base classes usually creates such a burden on derived types that you should avoid implementing ICloneable in nonsealed classes.
如果你运行这个程序,你将会发现d2的值是null。派生类从BaseType继承了ICloneable.Clone(),但是对于派生类型来说,这个实现是不正确的。它仅仅克隆了基类。BaseType.Clone()创建一个BaseType对象,而不是一个派生类的对象。这就是为什么在测试程序里d2是null,它不是派生的对象。然而,甚至假设你克服了这个困难,BaseType.Clone()也不会恰当的拷贝在派生类里面定义的dValues数组。当你实现ICloneable时,就强迫所有的派生类也要实现它。事实上,你应该提供一个钩子函数,让所有的派生类来使用你的实现(见Item 21)。为了支持克隆,派生类只能增加值类型的成员或者实现了ICloneable的类型的成员。这对所有的派生类来说是非常严格的限制。向基类添加ICloneable通常创建为派生类创建了负担,你应该避免在非密封类里面实现ICloneable。
When an entire hierarchy must implement ICloneable, you can create an abstract Clone() method and force all derived classes to implement it.
当一个整体的继承体系必须实现ICloneable时,你可以创建抽象的Clone()方法,强制所有的派生类继承它。
In those cases, you need to define a way for the derived classes to create copies of the base members. That's done by defining a protected copy constructor:
在那样的情况下,你需要为所有的派生类定义一个方式,来为基类成员创建一个拷贝。通过定义一个保护性的构造函数,可以达到这个目的。
- 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[];
- }
- static void Main(string[] args)
- {
- Derived d = new Derived();
- Derived d2 = d.Clone() as Derived;
- if (d2 == null)
- Console.WriteLine("null");
- }
- public object Clone()
- {
- Derived rVal = new Derived(this);
- return rVal;
- }
- }
Base classes do not implement ICloneable; they provide a protected copy constructor that enables derived classes to copy the base class parts. Leaf classes, which should all be sealed, implement ICloneable when necessary. The base class does not force all derived classes to implement ICloneable, but it provides the necessary methods for any derived classes that want ICloneable support.
基类没有实现ICloneable;它们提供了一个保护性的副本构造函数,可以使派生类复制基类的部分。叶子类,应该是密封的,当需要时实现ICloneable。基类不强迫所有的派生类实现ICloneable,但是为任何需要ICloneable支持的派生类提供必要的方法
ICloneable does have its use, but it is the exception rather than rule. You should never add support for ICloneable to value types; use the assignment operation instead. You should add support for ICloneable to leaf classes when a copy operation is truly necessary for the type. Base classes that are likely to be used where ICloneable will be supported should create a protected copy constructor. In all other cases, avoid ICloneable.
ICloneable确实有用,但是它更是特例而不是规则。不应该为值类型添加ICloneable支持,使用赋值操作符就行了。当确实需要拷贝操作的时候,对于叶子类,应该添加ICloneable支持。在ICloneable将被支持的地方,会被使用的基类应该创建保护性的拷贝构造函数。在所有其它情况下,避免ICloneable。