Effective C# Item 27: Avoid ICloneable

Effective C# Item 27: Avoid ICloneable

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

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

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

None.gif public   struct  ErrorMessage
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
private int errCode;
InBlock.gif    
private int details;
InBlock.gif    
private string msg;
ExpandedBlockEnd.gif}

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

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

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

None.gif      class  BaseType : ICloneable
ExpandedBlockStart.gifContractedBlock.gif    
dot.gif {
InBlock.gif        
private string _label = "class name";
InBlock.gif        
private int[] _values = new int[10];
InBlock.gif
InBlock.gif
ContractedSubBlock.gifExpandedSubBlockStart.gif        
ICloneable 成员#region ICloneable 成员
InBlock.gif
InBlock.gif        
public object Clone()
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            BaseType rVal 
= new BaseType();
InBlock.gif            rVal._label 
= _label;
InBlock.gif            
for (int i = 0; i < _values.Length; i++)
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                rVal._values[i] 
= _values[i];
ExpandedSubBlockEnd.gif            }

InBlock.gif            
return rVal;
ExpandedSubBlockEnd.gif        }

InBlock.gif
ExpandedSubBlockEnd.gif        
#endregion

ExpandedBlockEnd.gif    }

None.gif
None.gif    
class  Derived : BaseType
ExpandedBlockStart.gifContractedBlock.gif    
dot.gif {
InBlock.gif        
private double[] _dValues = new double[10];
ExpandedBlockEnd.gif    }

None.gif
None.gif    
// 调用
None.gif
    Derived d  =   new  Derived();
None.gif    Derived d2 
=  d.Clone()  as  Derived;
None.gif    
if  (d2  ==   null )
ExpandedBlockStart.gifContractedBlock.gif    
dot.gif {
InBlock.gif        Console.WriteLine(
"null");
ExpandedBlockEnd.gif    }

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

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

None.gif      class  BaseType
ExpandedBlockStart.gifContractedBlock.gif    
dot.gif {
InBlock.gif        
private string _label;
InBlock.gif        
private int[] _values;
InBlock.gif
InBlock.gif        
protected BaseType()
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            _label 
= "class name";
InBlock.gif            _values 
= new int[10];
ExpandedSubBlockEnd.gif        }

InBlock.gif
InBlock.gif        
protected BaseType(BaseType right)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            _label 
= right._label;
InBlock.gif            _values 
= right._values.Clone() as int[];
ExpandedSubBlockEnd.gif        }

ExpandedBlockEnd.gif    }

None.gif
None.gif    
sealed   class  Derived : BaseType, ICloneable
ExpandedBlockStart.gifContractedBlock.gif    
dot.gif {
InBlock.gif        
private double[] _dValues = new double[10];
InBlock.gif
InBlock.gif        
public Derived()
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            _dValues 
= new double[10];
ExpandedSubBlockEnd.gif        }

InBlock.gif
InBlock.gif        
private Derived(Derived right)
InBlock.gif            : 
base(right)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            _dValues 
= right._dValues.Clone as int[];
ExpandedSubBlockEnd.gif        }

InBlock.gif
ContractedSubBlock.gifExpandedSubBlockStart.gif        
ICloneable 成员#region ICloneable 成员
InBlock.gif
InBlock.gif        
public object Clone()
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            Derived rVal 
= new Derived(this);
InBlock.gif            
return rVal;
ExpandedSubBlockEnd.gif        }

InBlock.gif
ExpandedSubBlockEnd.gif        
#endregion

ExpandedBlockEnd.gif    }

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

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

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

      回到目录
 

转载于:https://www.cnblogs.com/aiyagaze/archive/2007/04/07/684607.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值