构建可克隆的对象(ICloneable)

请先看下面一断代码
public class Point:ICloneable
{
        private int x;
        public int X
        {
            get { return this.x; }
            set { this.x = value; }
        }
        public Point(int x)
        {
            this.x = x;
        }
        public override string ToString()
        {
            return string.Format("X={0}", this.x);
        }
}
 static void Main(string[] args)
        {
            //指向同一对象的两个引用
            Point p1 = new Point(100);
            Point p2 = p1;
            p1.X = 200;
            Console.WriteLine(p1.ToString());  //x=200
            Console.WriteLine(p2.ToString());  //x=200
            Console.Read();
        }

我们明明只是改变了p1对象的x属性而以,为什么连p2的x属性也跟着改呢?
我们知道引用类型和值类型的区别,如果把一个引用变量的值赋于另一个引用变量的话,这将使两个引用变量指向同一个对象,所以上面的赋值导致两个引用指向堆上的同一个Point对象,通过修改任务一个引用都可修改堆上的同样对象.

如果我们想让自己定义的类型(引用类型)也支持向调用方返回自身同样副本的能力,同时又不是引用同一对象的话,需要实现标准的ICloneable接口.以下是这个接口的源代码.
Public Interface Icloneable
{
   object Clone();
}

提示:不同对象的Clone()方法的实现都不一样!但思想却是一样的!都是将成员变量的值复制到新的对象实例中,然后向用户返回该实例.
我们就修改一下上面的Point类
例:

public class Point:ICloneable:ICloneable
{
        private int x;
        public int X
        {
            get { return this.x; }
            set { this.x = value; }
        }
        public Point(int x)
        {
            this.x = x;
        }
        public override string ToString()
        {
            return string.Format("X={0}", this.x);
        }

        #region ICloneable 成员

        public object Clone()
        {
            return new Point(this.x);
        }

        #endregion
}

再修改一下Main方法代码
static void Main(string[] args)
        {
            //指向同一对象的两个引用
            Point p1 = new Point(100, 100);
            Point p2 = (Point)p1.Clone();
            p1.X = 200;
            Console.WriteLine(p1.ToString()); //x=200
            Console.WriteLine(p2.ToString()); //x=100
            Console.Read();
        }
看上去已经是大功告成了!提示,如果自定义类型里面的属性全是值类型的话,我们可以使用更简洁的代码实现Clone()方法
修改Point类的Clone()方法内容
public object Clone()
{
     return this.MemberwiseClone();  
}

注意,如果Point包含任何引用类别的成员的话,那MemberwiseClone()将这些引用复制到对象中(浅复制),如果想支持真正的深复制的话,需要在克隆过程中创建任何引用类型变量的新实例,让我们接着看下面的例子吧!

我们在Point类中添加一个引用类型的成员
private Person person;
public Person Person
{
    get { return this.person; }
    set { this.person = value; }
}
同时修改Point的构造方法和ToString()方法以及Main方法
public Point(int x, Person person)
{
    this.x = x;
    this.person = person;
}
public override string ToString()
{
     return string.Format("X={0},Person.Age={1}", this.x, this.person.Age);
}
static void Main(string[] args)
{
            //指向同一对象的两个引用
            Point p1 = new Point(100, new Person(21));
            Point p2 = (Point)p1.Clone();
            p1.X = 200;
            p1.Person.Age = 22;
            Console.WriteLine(p1.ToString());
            Console.WriteLine(p2.ToString());
            Console.Read();
}
输出的结果大家按F5看看便知!那我们如何解决这种情况呢,上面已经说了"如果想支持真正的深复制的话,需要在克隆过程中创建任何引用类型变量的新实例"
请看修改后的Clone()方法代码
public object Clone()
{
   return new Point(this.x,new Person(this.Person.Age));
}

再看运行结果,呵呵,这不就是我们想要的结果吗?

小结:克隆过程中,如果一个类型只是包含值类型或者结构的话,使用MemberwiseClone()实现Clone方法,如果有一个保存其他引用类型的自定义类型,需要建立一个考虑每个引用类型成员变量的新的类型.

 

posted on 2010-05-30 22:42  Ss_Andy  阅读(316)  评论(0编辑  收藏  举报