Effective C#原则27:避免使用ICloneable

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" );
  }
}

时间: 2024-10-30 22:55:30

Effective C#原则27:避免使用ICloneable的相关文章

Effective C#原则50:了解ECMA标准

ECMA标准是C#语言所有功能的官方说明.ECMA-334定义了C#语言1.0的标准, 你可以从The C# Programming Language这本书上学习C#2.0的计划(译注:现在 已经不是计划了),这本书的作者是Anders Hejlsberg, Scott Wiltamuth, 和 Peter Golde (Addison-Wesley, 2003).这本书是一个语言手册,而不是指南. 它详细说明了这门语言书面定义的每一个功能.每一种语言都只一种标记,可以 让你更加明白每一种语言的

Effective C#原则48:了解更多的工具和资源

对于C#以及.Net来说这是激动人心的时候.这些工具目前还是比较新的,整 个社区都在学习如何使用这些工具.一些资源可以帮助你提高你的知识,以及为 .Net和C#创建一个更大的知识社区.这些工具是我每天都向C#开发人员推荐的. 关于C#实践的全部内容还在写作当中,跟进它们而且不断了解相关的内容. 第一个应该在每一个C#开发人员的工具箱的工具是NUnit, 它可以在 www.nunit.org网站上找到.NUnit是一个自动进行单元测试的工具,功能和 JUnit很像.和其它大多数开发人员一样,我讨厌

Effective C#原则47:选择安全的代码

.Net运行时已经设计好了,一些怀有恶意的代码不能渗透到远程计算机上并 执行.目前一些分部式系统依懒于从远程机器上下载和执行代码.如果你可以通 过Internet或者以太网来发布你的软件,或者直接从web上运行,但你须要明白 CRL在你的程序集中的一些限制.如果CLR不是完全相信一个程序集,它会限制一 些的行为.这些调用代码要有访问安全认证(CAS).从另一方面来说,CLR强制要 求基于角色的安全认证,这样这些代码才能或者不能在基于一个特殊的角色帐号 下运行. 安全违例是运行时条件,编译器不能强

Effective C#原则45:选择强异常来保护程序

当你抛出异常时,你就在应用程序中引入了一个中断事件.而且危机到程序 的控制流程.使得期望的行为不能发生.更糟糕的是,你还要把清理工作留给最 终写代码捕获了异常的程序员.而当一个异常发生时,如果你可以从你所管理的 程序状态中直接捕获,那么你还可以采取一些有效的方法.谢天谢地,C#社区不 须要创建自己的异常安全策略,C++社区里的人已经为我们完成了所有的艰巨的 工作.以Tom Cargill的文章开头:"异常处理:一种错误的安全感觉, " 而且Herb Sutter,Scott Meyer

Effective C#原则43:请勿滥用反射

创建二进制的组件时,同时也意味着你要使用迟后绑定和反射来查找你所须 要的具有特殊功能代码.反射是一个很有力的工具,而且它让你可以写出可动态 配置的软件.使用反射,一个应用程序可以通过添加新的组件来更新功能,而这 些组件是在软件最开始发布时没有的.这是有利的. 这一伸缩性也带来 了一些复杂的问题,而且复杂问题的增加又会增加出现其它问题的可能.当你使 用反射时,你是围绕着C#的安全类型.然而,成员调用的参数和返回值是以 System.Object类型存在的.你必须在运行时确保这些类型是正确的.简单的

Effective C#原则42:使用特性进行简单的反射

当你创建了一个与反射相关的系统时,你应该为你自己的类型,方法,以及 属性定义一些自己的特性,这样可以让它们更容易的被访问.自定义的特性标示 了你想让这些方法在运行时如何被使用.特性可以测试一些目标对象上的属性. 测试这些属性可以最小化因为反射时可能而产生的类型错误. 假设你须 要创建一个机制,用于在运行时的软件上添加一个菜单条目到一个命令句柄上.这个须要很简单:放一个程序集到目录里,然后程序可以自己发现关于它的一些 新菜单条目以及新的菜单命令.这是利用反射可以完成的最好的工作之一:你的 主程序须

Effective C#原则41:选择DataSet而不是自定义的数据结构

因为两个原则,把DataSet的名声搞的不好.首先就是使用XML序列化的 DataSet与其它的非.Net代码进行交互时不方便.如果在Web服务的API中使用 DataSet时,在与其它没有使用.Net框架的系统进行交互时会相当困难.其次, 它是一个很一般的容器.你可以通过欺骗.Net框架里的一些安全类型来错误 DataSet.但在现代软件系统中,DataSet还可以解决很多常规的问题.如果你明 白它的优势,避免它的缺点,你就可以扩展这个类型了. DataSet类设计 出来是为了离线使用一些存储

Effective C#原则40:根据需求选择集合

"哪种集合是最好的?"答案是:"视情况而定." 不同的集合有不同的性能,而且在不同的行为上有不同的优化..Net框架支持很 多类似的集合:链表,数组,队列,栈,以及其它的一些集合.C#支持多维的数 组,它的性能与一维的数组和锯齿数组都有所不同..Net框架同样包含了很多特 殊的集合,在你创建你自己的集合类之前,请仔细参阅这些集合.你可以发现很 多集合很快,因为所有的集合都实现了ICollection接口.在说明文档中列出了 所有实现了ICollection接口的集合

Effective C#原则37:使用标准的配置机制

我们要寻求一种避免直接写代码的应用程序配置和信息设置方法,我们已经 创建了多种不同的策略来存储配置信息.而我们是要寻求一种正确的方法,我们 要不断提高和改我们的想法,关于哪里是放置这些信息的好地方.INI文件?这 是Windows3.1做的事,配置信息的结构是受限制的,而且在文件名上可能还会与 其它程序程序相冲突.注册表?是的,是这个正确的想法,但它也有它的限制. 乱七八糟的程序可能会通过在注册表里写一些错误信息来严重破坏计算机.正因 为写注册表存在危险,一个应用程序必须有管理员权限来写注册表的