ICloneable看上去是个不错的主意:为一个类型实现ICloneable接口后就可 以支持拷贝了。如果你不想支持拷贝,就不要实现它。
但你的对象并不 是在一个“真空”的环境中运行,但考虑到对派生类的些影响,最好 还是对ICloneable支持。一但某个类型支持ICloneable, 那么所有的派生类都必 须保持一致,也就是所有的成员必须支持ICloneable接口或者提供一种机制支持 拷贝。最后,支持深拷贝的对象,在创建设计时如果包含有网络结构的对象,会 使拷贝很成问题。ICloneable也觉察到这个问题,在它的官方定义中有说明:它 同时支持深拷贝和浅拷贝。浅拷贝是创建一个新的对象,这个新对象对包含当前 对象中所有成员变量的拷贝。如果这些成员变量是引用类型的,那么新的对象与 源对象包含了同样的引用。而深拷贝则可以很好的拷贝所有成员变量,引用类型 也被递归的进行了拷贝。对于像整型这样的内置类型,深拷贝和浅拷贝是一样的 结果。哪一种是我们的类型应该支持的呢?这取决于类型本身。但同时在一个类 型中混用深拷贝和浅拷贝会导致很多不一致的问题。一但你涉及到ICloneable这 个问题,这样的混用就很难解脱了。大多数时候,我们应该完全避免使用 ICloneable,让类更简单一些。这样使用和实现都相对简单得多。
任何 只以内置类型做为成员的值类型不必支持ICloneable; 用简单的赋值语句对结构 的所有值进行拷贝比Clone()要高效得多。Clone()方法必须对返回类型进行装箱 ,这样才能强制转化成一个System.Object的引用。而调用者还得再用强制转化 从箱子中取回这个值。我知道你已经有足够的能力这样做,但不要用Clone()函 数来取代赋值语句。
那么,当一个值类型中包含一个引用类型时又会怎 样呢?最常见的一种情况就是值类型中包含一个字符串:
public struct ErrorMessage
{
private int errCode;
private int details;
private string msg;
// details elided
}
字符串是一个特殊情况,因为它是一个恒定类。如果你指定了一 个错误消息串,那么所有的错误消息类都引用到同一个字符串上。而这并不会导 致任何问题,这与其它一般的引用类型是不一样的。如果你在任何一个引用上修 改了msg变量,你会就为它重新创建了一个string对象(参见原则7)。
(译 注:string确实是一个很有意思的类,很多C++程序员对这个类不理解,也很有 一些C#程序对它不理解,导致很多的低效,甚至错误问题。应该好好的理解一下 C#里的string(以及String和StringBulider之间的关系)这个类,这对于学好C# 是很有帮助的。因为这种设计思想可以沿用到我们自己的类型中。)
一般 情况,如果一个结构中包含了一个任意的引用类型,那么拷贝时的情况就复杂多 了。这也是很少见的,内置的赋值语句会对结构进行浅拷贝,这样两个结构中的 引用变量就引用到同个一个对象上。如果要进行深拷贝,那么你就必须对引用类 型也进行拷贝,而且还要知道该引用类型上是否也支持用Clone()进行深拷贝。 不管是哪种情况,你都不用对值类型添加对ICloneable的支持,赋值语句会对值 类型创建一个新的拷贝。
一句概括值类型:没有任何理由要给一个值类 型添加对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" );
}
}