Effective C# Item 27: Avoid ICloneable

      ICloneable听起来不错:通过它我们就可以让类型支持拷贝。但是我们的类往往并不是独立存在的。一旦我们确定要支持ICloneable,那么它的所有派生类也需要实现这个接口。内部的所有成员也需要支持ICloneable或者其它创建拷贝的机制。要实现深克隆经常会带来很多的问题。在理论上ICloneable是用来解决这些克隆问题的:它可以支持浅克隆或者深克隆。浅克隆创建一个新的对像,对像成员的内容都是原内容的拷贝。对于引用类型来说,新的对像和原对像引用的地址是一样的。而深克隆也是创建一个新对像并拷贝其成员。所有的引用类型都被递归克隆,而不再引用原对像成员。对于.Net内建值类型,例如int来说,这两种克隆方式的结果没有区别。究竟支持哪种要取决于我们的类。但是混合了潜克隆和深克隆机制的类会给我们造成一些错误。一旦掉进了ICloneable的陷阱,就很难全身而退了。一般情况下尽量将类创建得简单,避免使用ICloneable。这样既便于使用,也便于实现。

      任何只包含内建类型成员的值类型是不需要支持ICloneable的。简单的拷贝值到结构体中比使用Clone()更加高效。Clone()在返回值时需要装箱操作,在使用返回值时又需要对其拆箱。

      那么如果值类型中包含引用类型呢?最常见的例子就是一个结构体中包含string型成员:

public struct ErrorMessage
{
    
private int errCode;
    
private int details;
    
private string msg;
}

      string是一个特例因为它是不可变类型(参见 Item7: Prefer Immutable Atomic Value Types)。它不会引发一般的引用类型造成的麻烦。因为一旦msg的值变化了,就代表它指向了一个新的string对像的引用。

      如果结构中包含其它的引用类型,那么情况就会复杂的多。而且这种情况也是很常见的。当我们使用内建的拷贝操作(最简单的=操作符)时,创建的是一个潜克隆对像,其中的引用类型成员同原结构体中的成员指向同一个对像。如果要创建深拷贝,我们首先要确定结构中的引用类型是否支持深拷贝。

      我们使用ICloneable的时候必须谨慎,因为我们需要考虑到派生类的情况。先看下面这个例子:

    class BaseType : ICloneable
    
{
        
private string _label = "class name";
        
private int[] _values = new int[10];


        
ICloneable 成员
    }


    
class Derived : BaseType
    
{
        
private double[] _dValues = new double[10];
    }


    
//调用
    Derived d = new Derived();
    Derived d2 
= d.Clone() as Derived;
    
if (d2 == null)
    
{
        Console.WriteLine(
"null");
    }

      运行后我们会发现d2为null。派生类的确调用了基类的ICloneable.Clone()方法,但是返回的却是一个基类的对像,而不是派生类的。这也是为什么d2为null的原因。而且即便解决了这个问题,派生类中的_dValues也不能成功的克隆。我们需要提供一个钩子函数来使得所有的派生类支持克隆。而且派生类中新成员必须是值类型或者支持克隆的引用类型。对于派生类来说,这是一个有些苛刻的要求。支持ICloneable往往会给我们带来额外的负担。

      如果必须支持ICloneable,我们可以创建一个抽象Clone()方法来让所有的派生类实现。这样我们还需要为派生类提供一个创建基类对像拷贝的方法。下例中通过定义一个受保护的拷贝构造函数来实现:

    class BaseType
    
{
        
private string _label;
        
private int[] _values;

        
protected BaseType()
        
{
            _label 
= "class name";
            _values 
= new int[10];
        }


        
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];
        }


        
private Derived(Derived right)
            : 
base(right)
        
{
            _dValues 
= right._dValues.Clone as int[];
        }


        
ICloneable 成员
    }

      上例中基类没有实现ICloneable,它提供了一个受保护的构造函数来让派生类拷贝基类成员。对于声明为sealed的派生类可以在必要时实现ICloneable。这样虽然基类提供了对克隆的必要支持,也不要求所有的派生类都实现克隆。

      ICloneable虽然有它的优势,但是也会造成很多麻烦。永远不要让值类型支持ICloneable。尽量避免在不必要的情况下使用ICloneable接口。

      译自   Effective C#:50 Specific Ways to Improve Your C#                      Bill Wagner著

      回到目录
 

posted on 2007-04-07 16:10  aiya  阅读(879)  评论(1编辑  收藏  举报