实现ICloneable接口,看起来是个不错的选择,想要类型支持拷贝,就实现ICloneable,不想支持拷贝,就不实现ICloneable。但是,大家仔细想一想,你的对象并不是在一个独立的环境中运行,需要考虑到对派生类的影响,基类已经实现了ICloneable接口,派生类也继承了基类的Clone方法,所以派生类最好也支持ICloneable接口,这样所有派生类都应保持一致,所以所有派生类都应实现ICloneable接口,还需要考虑到类的成员都必须支持ICloneable接口或提供一种机制支持拷贝,如果支持深拷贝的对象包含有网状结构的对象,就会使拷贝很成问题。
   这里谈到了深拷贝,对应的是浅拷贝。所谓浅拷贝,是创建一个新对象,然后将原对象的所有成员拷贝到新对象中,如果某成员是引用类型,那么仅仅拷贝引用给新对象,也就是说新,旧对象的那个成员引用的是同一个对象。而深拷贝是拷贝所有成员并对引用类型的对象进行递归的拷贝,即对引用的对象也进行拷贝(而不仅仅是引用的拷贝,并且是递归下去,直到递归到只有值类型成员的拷贝)。像值类型和String类型,深拷贝和浅拷贝效果一样,都是完全复制,得到一个与原对象无关新对象(只有内容一样,没有其它关联,对两者中任何一个的修改,不会影响到另一个对象)。
   那么当我们定义的类型实现了ICloneable接口时,我们应该实现深拷贝还是浅拷贝呢,这取决于类型本身,但同时在一个类型中混用深拷贝和浅拷贝会导致很多不一致问题,一旦实现了ICloneable接口,这种混用就很难避免,所以定义类型时应该尽量避免实现ICloneable接口,让类更简单一些,使用和实现都相对简单一些。

  任何只以值类型和String类型对象作为成员的值类型都不用支持ICloneable接口,用简单的赋值语句要比Clone方法高效得多,Clone方法要对返回的对象进行装箱,才能强制返回一个System.Object对象,而调用者使用对象时又要对其进行拆箱,这又是何苦呢?例如:

public struct ErrorMessage
    {
        private int _errorCode;
        private int _details;
        private string _msg;
    }
这时可能大家会产生一个疑问:string类型是引用类型,为什么一个简单的赋值语句就能实现_msg的深拷贝,而不用显示的去实现,这是因为string类型的恒定性,对string类型对象的任何操作都会产生一个新的string对象,并返回其引用。string确实是一个很有意思的类,很多C++程序员对这个类不理解,也很有一些C#程序对它不理解,导致很多的低效,甚至错误问题。应该好好的理解一下C#里的string(以及String和StringBulider之间的关系)这个类,这对于学好C#是很有帮助的。因为这种设计思想可以沿用到我们自己的类型中。
如果一个结构体包含一个一般的引用类型(除了字符串外),那么这时拷贝的情况就复杂的多。内置的赋值操作只会对结构体进行浅拷贝,这样两个结构体内部的引用类型的成员引用的是同一个对象。如果要进行深拷贝,那就要实现ICloneable接口,并且在Clone方法中对该成员变量引用的对象进行深拷贝,这就必须知道该对象是否实现ICloneable接口,并在该对象的Clone()方法中也支持深拷贝。当然,由于这种在值类型中包含引用类型成员的情况比较少见,所以我们还是没有理由对值类型实现ICloneable接口。
而对于引用类型来说,只在确实有必要支持拷贝时,才在叶子类(继承体系的末端,即标识为sealed的类)中实现ICloneable接口。为什么说只能在叶子类中实现ICloneable接口呢?看看下面这个例子:
public class BaseType : ICloneable
    {
        private string _label = "PeterLau";
        private int[] _values = new int[10];

public object Clone()
        {
            BaseType baseType = new BaseType();
            baseType._label = this._label;
            baseType._values = this._values;
            return baseType;
        }
    }

public class DerivedType : BaseType
    {
        private double[] _dValues=new double[10];

static void Main()
        {
            DerivedType derivedType1 = new DerivedType();
            DerivedType derivedType2 = derivedType1.Clone() as DerivedType;
            if (derivedType2 == null)
                Console.WriteLine("null");
        }
    }

上述这段代码的输出是"null",很明显当子类对象调用Clone()方法时,其实是调用的基类的Clone()方法,该方法返回的是基类的对象,当使用as转换为子类对象时当然返回的是null。即使你解决了这一个问题,基类的Clone()方法也不可能拷贝子类的_dValues字段。所以一旦你的类型实现了ICloneable接口,那么就强迫你的派生类也要正确的实现ICloneable接口(因为子类继承了基类的Clone方法,在客户端可以通过子类对象调用Clone方法,但是调用返回的结果又是错的),在基类中实现ICloneable接口会给派生类带来这样的负担,所以当你的引用类型要实现ICloneable接口时,最好将你的类型定义为sealed。如果你的基类实现了ICloneable接口,于是你的整个继承体系都要实现ICloneable接口时,这时你可以在基类中定义一个抽象的Clone方法,强迫所有派生类提供Clone方法的实现。当然这是出于安全考虑,最好还是不要在非密封类中实现ICloneable接口。

当在密封子类中实现ICloneable接口时,需要在基类中定义一个protected的构造函数,以便在子类的Clone方法中拷贝基类成员。例如:
public class BaseType
    {
        private string _label = "PeterLau";
        private int[] _values = new int[10];

public BaseType()
        { }

protected BaseType(BaseType baseType)
        {
            _label = baseType._label;
            _values = baseType._values.Clone() as int[];
        }
    }

public class DerivedType : BaseType,ICloneable
    {
        private double[] _dValues=new double[10];

public DerivedType()
        {
            
        }
        
        private DerivedType(DerivedType right)
            : base(right)
        {
            _dValues = right._dValues;
        }

public object Clone()
        {
            DerivedType derivedType = new DerivedType(this);
            return derivedType;
        }

static void Main()
        {
            DerivedType derivedType1 = new DerivedType();
            DerivedType derivedType2 = derivedType1.Clone() as DerivedType;
            if (derivedType2 == null)
                Console.WriteLine("null");
        }
    }

基类并不实现ICloneable接口; 通过提供一个受保护的构造函数,让派生类可以拷贝基类的成员。叶子类,应该都是密封的,必要它应该实现ICloneable接口。基类不应该强迫所有的派生类都要实现ICloneable接口,但你应该提供一些必要的方法,以便那些希望实现ICloneable接口的派生类可以使用。

ICloneable接口有它的用武之地,但相对于它的规则来说,我们应该避免它。对于值类型,你不应该实现ICloneable接口,应该使用赋值语句。对于引用类型来说,只有在拷贝确实有必要存在时,才在叶子类上实现对ICloneable的支持。基类在可能要对ICloneable 进行支持时,应该创建一个受保护的构造函数。总而言之,我们应该尽量避免使用ICloneable接口。

转载于:https://www.cnblogs.com/net-liu/archive/2009/09/28/1571410.html

Item 27 避免使用ICloneable接口相关推荐

  1. Effective Modern C++ Item 27 熟悉依万能引用型别进行重载的替代方案

    Item 27 熟悉依万能引用型别进行重载的替代方案 Item 26说过,万能引用和重载在一起总会产生各种各样的问题,无论是独立函数,成员函数,都最好不要和万能引用放一起重载,其中构造函数和万能引用放 ...

  2. Effective C#:避免使用ICloneable接口

    最近在学习Bill Wagner的书籍:<Effective C#:50 Specific Ways to Improve Your C#>,虽然是一本很早的书了,但是感觉很实用,并且跟国 ...

  3. C#的System.ICloneable接口说明

    原理 如果我们有两个值类型的变量,将其中一个变量的值赋给另一个,实际上会创建该值的一个副本,这个副本与原来的值没有什么关系--这意味着改变其中一 个的值不会影响另一个变量的值.而如果是两个引用类型的变 ...

  4. [c#基础]ICloneable接口

    摘要 该接口使你能够创建现有对象的副本的自定义的实现.该接口只提供了,一个Clone方法,实现对象的浅拷贝.有浅拷贝,那么就有相对应的深拷贝.但该接口并没有对我们提供,需要我们自己实现. 什么是浅拷贝 ...

  5. 读书笔记 effective c++ Item 41 理解隐式接口和编译期多态

    1. 显示接口和运行时多态 面向对象编程的世界围绕着显式接口和运行时多态.举个例子,考虑下面的类(无意义的类), 1 class Widget { 2 public: 3 Widget(); 4 vi ...

  6. 2022/10/26 OR 27 关于java的接口初识 猫狗接口/教练学员案例

    接口的特点: 接口的关键字 public interface 接口名() 类实现接口的关键字 public class 类名 implements 接口名()  接口的实现参照多态:抽象类多态,接口多 ...

  7. 提高C#编程水平的50个要点

    1.总是用属性 (Property) 来代替可访问的数据成员 2.在 readonly 和 const 之间,优先使用 readonly 3.在 as 和 强制类型转换之间,优先使用 as 操作符 4 ...

  8. 提高C#编程水平的50个要诀[转载]

    一篇旧时的文章,看后觉得还可以,特别贴出来. 提高C#编程水平的50个要点: 1.总是用属性 (Property) 来代替可访问的数据成员 2.在  readonly 和 const 之间,优先使用 ...

  9. 提高C#编程水平的50个技巧

    转自:网络 1.总是用属性 (Property) 来代替可访问的数据成员 2.在  readonly 和 const 之间,优先使用 readonly 3.在 as 和 强制类型转换之间,优先使用 a ...

  10. 提高.NET编程水平的50个要点(转载)

    1.总是用属性 (Property) 来代替可访问的数据成员 2.在  readonly 和 const 之间,优先使用 readonly 3.在 as 和 强制类型转换之间,优先使用 as 操作符 ...

最新文章

  1. inittab 文件分析
  2. Autohotkey puretext
  3. 小游戏专场:腾讯云Game-Tech技术沙龙上海站顺利落下帷幕
  4. jvm内存结构_浅谈JVM内存结构
  5. CSHOP后台设置SMTP发邮件提示 Error: need RCPT command 错误解决
  6. 东北大学计算机 大一物理考试题,东北大学大学物理期末考题及答案Word版
  7. 报错:The type javax.servlet.http.HttpServletRequest cannot be resolved
  8. php 时间选择,PHP-在学说2中的日期之间选择条目
  9. shell练习DAY14
  10. 路由器-配置(思科)
  11. ios cell点击对勾_带图像和对勾的iOS自定义TableView
  12. fpgrowth算法实战 mlib_MLlib--FPGrowth算法
  13. 控制理论个人学习笔记-非线性系统理论
  14. C#对STK11.4二次开发的Hello World
  15. android 手机内存64实际不到,我手机64G都天天清理,为什么内存越来越少?原来方法不对...
  16. ubuntu14.04 clementine音乐播放器无法播放ape格式解决方法
  17. 米老师经典语录————再造生命力
  18. sql函数RIGHT的简单用法
  19. 2022-02-11 学习记录:通过CSS3的clip-path实现多边形
  20. Python 爬抖音

热门文章

  1. python日历下拉框_selenium+Python(Js处理日历控件)
  2. Collectors.averagingLong()
  3. 【渝粤教育】国家开放大学2018年秋季 0299-22T中国古代文学(1) 参考试题
  4. 【渝粤教育】国家开放大学2018年春季 8038-22T实用管理基础 参考试题
  5. [渝粤教育] 西南科技大学 运输组织学 在线考试复习资料
  6. 数据科学家应该掌握的12种机器学习算法(附信息图)
  7. [转]SQL2008关于c001f011的错误解决办法
  8. 51NOD 1181 质数中的质数(质数筛法)
  9. 用Unity简单实现第三人称人物的移动和转向
  10. 欧拉定理、费马小定理及其拓展应用