Effective C# Item27:避免ICloneable接口
一般情况下,我们不建议针对类型实现ICloneable接口。因为如果一个类型支持ICloneable接口,那么该类型的所有派生类都必须实现它,而且类型中所有成员类型也都要实现ICloneable接口,或者有其他创建复制对象的机制,当我们设计的类型包含交织成网状的对象时,支持深复制会变得比较复杂。
复制操作分为浅复制和深复制两种,其中浅复制是指新对象包含所有成员变量的副本,如果成员变量为引用类型,那么新对象将和原对象引用同样的对象;深复制中新对象也包含所有成员变量的副本,但是所有引用类型的成员变量都会被递归的复制。对于C#来说,内建类型,例如整型,执行深复制和浅复制的结果是相同的。
任何只包含内建类型成员的值类型都不需要实现ICloneable接口,一个简单的赋值语句要比Clone()方法效率更高,因为Clone()方法必须对返回值进行装箱,才能转换成一个System.Object引用。
而对于引用类型来说,如果引用类型需要通过实现ICloneable接口的方式来表明自身支持浅复制或者深复制,这时,该类型的派生类也必须实现ICloneable接口。
来看下面的代码。
1 class BaseType : ICloneable
2 {
3 private string _label = "class name";
4 private int [] _values = new int [ 10 ];
5
6 public object Clone()
7 {
8 BaseType rVal = new BaseType( );
9 rVal._label = _label;
10 for( int i = 0; i < _values.Length; i++ )
11 rVal._values[ i ] = _values[ i ];
12 return rVal;
13 }
14 }
15
16 class Derived : BaseType
17 {
18 private double [] _dValues = new double[ 10 ];
19
20 static void Main( string[] args )
21 {
22 Derived d = new Derived();
23 Derived d2 = d.Clone() as Derived;
24
25 if ( d2 == null )
26 Console.WriteLine( "null" );
27 }
上述代码在运行后,我们会发现d2的值时null,这是因为Derived类从BaseType类继承了Clone()方法,但是继承来的实现对Derived类型来说是不正确的,因为它只是克隆了基类,BaseType.Clone()方法创建了一个BaseType类型的对象,而不是一个Derived类型的对象。
如果需要解决这个问题,我们可以将BaseType类中的Clone()方法声明为abstract,这样所有的派生类都必须实现这个方法。另外,我们还可以通过以下的方式来解决这个问题。
1 class BaseType
2 {
3 private string _label;
4 private int [] _values;
5
6 protected BaseType( )
7 {
8 _label = "class name";
9 _values = new int [ 10 ];
10 }
11
12 // Used by devived values to clone
13 protected BaseType( BaseType right )
14 {
15 _label = right._label;
16 _values = right._values.Clone( ) as int[ ] ;
17 }
18 }
19
20 sealed class Derived : BaseType, ICloneable
21 {
22 private double [] _dValues = new double[ 10 ];
23
24 public Derived ( )
25 {
26 _dValues = new double [ 10 ];
27 }
28
29 // Construct a copy
30 // using the base class copy ctor
31 private Derived ( Derived right ) :
32 base ( right )
33 {
34 _dValues = right._dValues.Clone( )
35 as double[ ];
36 }
37
38 static void Main( string[] args )
39 {
40 Derived d = new Derived();
41 Derived d2 = d.Clone() as Derived;
42 if ( d2 == null )
43 Console.WriteLine( "null" );
44 }
45
46 public object Clone()
47 {
48 Derived rVal = new Derived( this );
49 return rVal;
50 }
51 }
总之,对于值类型来讲,我们永远都不需要实现ICloneable接口,使用默认的赋值操作就可以了,我们应该为那些确实需要复制操作的”叶子类“实现ICloneable接口,对于那些子类可能需要实现ICloneable接口的基类来说,我们应该为其创建一个受保护的复制构造函数。除此之外,我们应该避免实现ICloneable接口。