构建可克隆的对象(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方法,如果有一个保存其他引用类型的自定义类型,需要建立一个考虑每个引用类型成员变量的新的类型.