C#的System.ICloneable接⼝说明
原理
如果我们有两个值类型的变量,将其中⼀个变量的值赋给另⼀个,实际上会创建该值的⼀个副本,这个副本与原来的值没有什么关系——这意味着改变其中⼀个的值不会影响另⼀个变量的值。⽽如果是两个引⽤类型的变量,其中⼀个变量的值赋给另⼀个的话(不包括string类型,CLR会对其有特殊处理),并没有创建值的副本,⽽是使两个变量执⾏同⼀个对象——这意味着改变对象的值会同时影响两个变量。要真正地创建引⽤类型的副本,我们必须克隆(clone)变量指向的对象。
实现ICloneable接⼝使⼀个类型成为可克隆的(cloneable),这需要提供Clone⽅法来提供该类型的对象的副本。Clone⽅法不接受任何参数,返回object类型的对象(不管是何种类型实现该接⼝)。所以我们获得副本后仍需要进⾏显式地转换。
实现ICloneable接⼝的⽅式取决于我们的类型的数据成员。如果类型仅包含值类型(int,byte等类型)和string类型的数据成员,我们只要在Clone⽅法中初始化⼀个新的对象,将其的数据成员设置为当前对象的各个成员的值即可。事实上,object类的 MemberwiseClone⽅法会⾃动完成该过程。
如果⾃定义类型包含引⽤类型的数据成员,必须考虑Clone⽅法是实现浅拷贝(shallow copy)还是深拷
贝(deep copy)。浅拷贝是指副本对象中的引⽤类型的数据成员与源对象的数据成员指向相同的对象。⽽如果是深拷贝,则必须创建整个对象的结构,副本对象中的引⽤类型的数据成员与源对象的数据成员指向不同的对象。
浅拷贝是容易实现的,就是使⽤前⾯提到的MemberwiseClone⽅法。开发⼈员往往希望使⽤的类型能够实现深拷贝,但会发现这样的类型并不多。这种情况在System.Collections命名空间中尤其常见,这⾥⾯的类在其Clone⽅法中实现的都是浅拷贝。这么做主要出于两个原因:
1. 创建⼀个⼤对象的副本对性能影响较⼤;
2. 通⽤的集合类型可能会包含各种各样的对象,在这种情况下实现深拷贝并不可⾏,因为集合中的对象并⾮都是可克隆的,另外还存在
循环引⽤的情况,这会让深拷贝过程陷⼊死循环。
对于强类型的集合情况有所不同,因为它包含的元素是可控制的,此时深拷贝变得有⽤,同时也是可⾏的。例如System.Xml.XmlNode在其Clone⽅法中实现了深拷贝。
另外,如果需要克隆⼀个未实现ICloneable接⼝却是可序列化的对象,通常可以通过序列化和反序列化来达到克隆的效果。但要⼩⼼,序列化过程不⼀定会序列化所以数据成员。
代码
下⾯的代码⽰例描述了克隆的各种⽅法。简单的Employee类仅仅包含string和int类型的成员,所以使⽤object类型的 MemberwiseClone⽅法创建副本。Team类的Clone⽅法实现了深拷贝,它包含了⼀个Employee对象的集合,同时Team类提供了⼀个私有的构造函数⽤以简化Clone⽅法的代码。构造函数的这种⽤法是简化克隆过程的⼀种常见⽅式。
public class Employee : ICloneable
{
public string Name;
public string Title;
public int Age;
// Simple Emplyee constructor
public Employee(string name, string title, int age)
{
Name = name;
Title = title;
Age = age;
}
public object Clone()
{
return MemberwiseClone();
}
public override string ToString()
{
return string.Format("{0} ({1}) - Age {2}", Name, Title, Age);
}
}
Team类代码:
public class Team : ICloneable
{
public List<Employee> TeamMembers = new List<Employee>();
clonepublic Team()
{
}
private Team(List<Employee> members)
{
foreach (Employee e in members)
{
TeamMembers.Add(e.Clone() as Employee);
}
}
// Adds an Employee object to the Team.
public void AddMember(Employee member)
{
TeamMembers.Add(member);
}
/
/ Override Object.ToString method to return a string representation of the team.    public override string ToString()
{
StringBuilder sb = new StringBuilder();
foreach (Employee e in TeamMembers)
{
sb.AppendFormat("  {0}\r\n", e);
}
return sb.ToString();
}
// Implementation of ICloneable.Clone.
public object Clone()
{
return new Team(this.TeamMembers);
// the following code would create a shallow copy of the team.
//return MemberwiseClone();
}
}
测试代码:
// Create the original team.
Team team = new Team();
team.AddMember(new Employee("Anders", "Developer", 26));
team.AddMember(new Employee("Bill", "Developer", 46));
team.AddMember(new Employee("Steve", "CEO", 36));
Team clone = team.Clone() as Team;
// Display the original team.
Console.WriteLine("Original Team:");
Console.WriteLine(team);
// Display the cloned team.
Console.WriteLine("Clone Team:");
Console.WriteLine(clone);
// Make changes.
Console.WriteLine("*** Make a change to original team ***");
Console.WriteLine(Environment.NewLine);
team.TeamMembers[0].Title = "PM";
team.TeamMembers[0].Age = 30;
// Display the original team. Console.WriteLine("Original Team:"); Console.WriteLine(team);
// Display the cloned team. Console.WriteLine("Clone Team:"); Console.WriteLine(clone);

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。