Rocho.J

人脑是不可靠的, 随时记录感悟并且经常重复!

 

【转】深复制和浅复制对象

C#中的深复制和浅复制(在C#中克隆对象)

最近开发中对泛型数据进行排序,但是还需要用到原始数据,所以就复制了该对象到另一个对象,忘记了引用类型是传地址的,所以出了bug,转贴一篇文章,记录一下自己的错误。

 

C# 支持两种类型:“值类型”和“引用类型”。 
值类型(Value Type)(如 char、int 和 float)、枚举类型和结构类型。 
引用类型(Reference Type) 包括类 (Class) 类型、接口类型、委托类型和数组类型。

如何来划分它们?
以它们在计算机内存中如何分配来划分

值类型与引用类型的区别?
1,值类型的变量直接包含其数据,
2,引用类型的变量则存储对象引用。
对于引用类型,两个变量可能引用同一个对象,因此对一个变量的操作可能影响另一个变量所引用的对象。对于值类型,每个变量都有自己的数据副本,对一个变量的操作不可能影响另一个变量。


值类型隐式继承自System.ValueType  所以不能显示让一个结构继承一个类,C#不支持多继承

堆栈(stack)是一种先进先出的数据结构,在内存中,变量会被分配在堆栈上来进行操作。
堆(heap)是用于为类型实例(对象)分配空间的内存区域,在堆上创建一个对象,
会将对象的地址传给堆栈上的变量(反过来叫变量指向此对象,或者变量引用此对象)。

关于对象克隆的所设计到知识点

浅拷贝:是指将对象中的所有字段逐字复杂到一个新对象
        对值类型字段只是简单的拷贝一个副本到目标对象,改变目标对象中值类型字段的值不会反映到原始对象中,因为拷贝的是副本
        对引用型字段则是指拷贝他的一个引用到目标对象。改变目标对象中引用类型字段的值它将反映到原始对象中,因为拷贝的是指向堆是上的一个地址

深拷贝:深拷贝与浅拷贝不同的是对于引用字段的处理,深拷贝将会在新对象中创建一个新的对象和
        原始对象中对应字段相同(内容相同)的字段,也就是说这个引用和原始对象的引用是不同, 我们改变新
        对象中这个字段的时候是不会影响到原始对象中对应字段的内容。

浅复制: 实现浅复制需要使用Object类的MemberwiseClone方法用于创建一个浅表副本
深复制: 须实现 ICloneable接口中的Clone方法,且需要需要克隆的对象加上[Serializable]特性

 

 

class DrawBase:System.Object , ICloneable
    {
        public string name = "jmj";
        public DrawBase()
        {
        } 
        
        public object Clone()
        {
            return this as object;      //引用同一个对象
            return this.MemberwiseClone(); //浅复制
            return new DrawBase() as object;//深复制
        }
    }
class Program
{
 
        static void Main(string[] args)
        {
            DrawBase rect = new DrawBase();
            Console.WriteLine(rect.name);
            DrawBase line = rect.Clone() as DrawBase;
            line.name = "a9fs3";
            Console.WriteLine(rect.name);
            DrawBase ploy = line.Clone() as DrawBase;
            ploy.name = "lj";
            Console.WriteLine(rect.name);
 
            Console.WriteLine(object.ReferenceEquals(line, ploy));
            Console.ReadLine();
        }
}
运行结果:
 return this as object;      //引用同一个对象
输出:jmj
        a9fs3
        lj
        True
 
return this.MemberwiseClone(); //浅复制
return new DrawBase() as object;//深复制 
输出均为: jmj 
             jmj
             jmj
             False
解释:
return this as object 方法总是引用同一个对象,因此相应的堆内存上的值会改变!
后两种方法都是对对象的复制,区别在于复制的类别不同:深复制会复制整个填充的对象,包括该对象中其他引用类型和值类型的值;而浅复制只复制了一个对象中所有引用,它没有值的复制,通过引用它们的其他对象的引用来共享它们。

 

两篇帖子原文:http://www.cnblogs.com/huangting2009/archive/2009/03/13/1410634.html
还有:http://blog.csdn.net/ifooler/archive/2007/05/06/1598452.aspx

 

 

 

 

第二部分:

           public object Copy(this object obj)  
        {  
            Object targetDeepCopyObj;  
            Type targetType = obj.GetType();  
            //值类型  
            if (targetType.IsValueType == true)  
            {  
                targetDeepCopyObj = obj;  
            }  
            //引用类型   
            else  
            {  
                targetDeepCopyObj = System.Activator.CreateInstance(targetType);   //创建引用对象   
                System.Reflection.MemberInfo[] memberCollection = obj.GetType().GetMembers();  
  
                foreach (System.Reflection.MemberInfo member in memberCollection)  
                {  
                    if (member.MemberType == System.Reflection.MemberTypes.Field)  
                    {  
                        System.Reflection.FieldInfo field = (System.Reflection.FieldInfo)member;  
                        Object fieldValue = field.GetValue(obj);  
                        if (fieldValue is ICloneable)  
                        {  
                            field.SetValue(targetDeepCopyObj, (fieldValue as ICloneable).Clone());  
                        }  
                        else  
                        {  
                            field.SetValue(targetDeepCopyObj, Copy(fieldValue));  
                        }  
  
                    }  
                    else if (member.MemberType == System.Reflection.MemberTypes.Property)  
                    {  
                        System.Reflection.PropertyInfo myProperty = (System.Reflection.PropertyInfo)member;  
                        MethodInfo info = myProperty.GetSetMethod(false);  
                        if (info != null)  
                        {  
                            object propertyValue = myProperty.GetValue(obj, null);  
                            if (propertyValue is ICloneable)  
                            {  
                                myProperty.SetValue(targetDeepCopyObj, (propertyValue as ICloneable).Clone(), null);  
                            }  
                            else  
                            {  
                                myProperty.SetValue(targetDeepCopyObj, Copy(propertyValue), null);  
                            }  
                        }  
  
                    }  
                }  
            }  
            return targetDeepCopyObj;  
        }  
View Code

 

 

 

 

 

//////////////////////////////

第三部分

 

C#中有两种类型变量,一种 是值类型变量,一种是引用类型变量,对于值类型变量,深拷贝和前拷贝都是通过赋值操作符号(=)实现,其效果一致,将对象中的值类型的字段拷贝到新的对象中.这个很容易理解。 本文重点讨论引用类型变量的拷贝机制和实现。

  C#中引用类型对象的copy操作有两种:

  浅拷贝(影子克隆/shallow copy):只复制对象的值类型字段,对象的引用类型,仍属于原来的引用. 深拷贝(深度克隆):不仅复制对象的值类型字段,同时也复制原对象中的对象.就是说完全是新对象产生的.

  浅拷贝和深拷贝之间的区别:浅拷贝是指将对象中的数值类型的字段拷贝到新的对象中,而对象中的引用型字段则指复制它的一个引用到目标对象。

  注意:string类型有点特殊,对于浅拷贝,类值类型对象进行处理。

  浅拷贝的实现

  使用Object类MemberwiseClone实现

  MemberwiseClone:创建当前 Object 的浅表副本。

  MemberwiseClone 方法创建一个浅表副本,方法是创建一个新对象,然后将当前对象的非静态字段复制到该新对象。如果字段是值类型的,则对该字段执行逐位复制。如果字段是引用类型,则复制引用但不复制引用的对象;因此,原始对象及其复本引用同一对象。

  代码实现如下:

 


     public class Person 
  { 
  public int Age { get; set; } 
  public string Address { get; set; } 
  public Name Name { get; set; } 
  public object Clone() 
  { 
  return this.MemberwiseClone(); 
  } 
  } 
  public class Name 
  { 
  public Name(string frisName,string lastName) 
  { 
  FristName = frisName; 
  LastName = lastName; 
  } 
  public string FristName { get; set; } 
  public string LastName { get; set; } 
  }

 

     赋值操作(=)VS使用Object类MemberwiseClone实现

  对于引用类型的变量,我们有种误解,认为赋值操作就是浅拷贝一种,其实不然,两者有区别。

  浅拷贝(shallow copy)对于引用类型对象中的值类型字段进行了逐位复制。赋值运算符只是把源对象的引用赋值给目的对象,两者引用同一个对象。 浅拷贝后的对象的值类型字段更改不会反映到源对象,而赋值运算后的对象的值类型字段更改会反映到源对象 代码实现如下:

  

 


     public class Person 
  { 
  public int Age { get; set; } 
  public string Address { get; set; } 
  public Name Name { get; set; } 
  } 
  public class Name 
  { 
  public Name(string frisName,string lastName) 
  { 
  FristName = frisName; 
  LastName = lastName; 
  } 
  public string FristName { get; set; } 
  public string LastName { get; set; } 
  }

 

  深拷贝实现

  相对于浅拷贝,是指依照源对象为原型,创建一个新对象,将当前对象的所有字段进行执行逐位复制并支持递归,不管是是值类型还是引用类型,不管是静态字段还是非静态字段。

  在C#中,我们们有三种方法实现深拷贝

  实现ICloneable接口,自定义拷贝功能。

  ICloneable 接口,支持克隆,即用与现有实例相同的值创建类的新实例。

  ICloneable 接口包含一个成员 Clone,它用于支持除 MemberwiseClone 所提供的克隆之外的克隆。Clone 既可作为深层副本实现,也可作为浅表副本实现。在深层副本中,所有的对象都是重复的;而在浅表副本中,只有顶级对象是重复的,并且顶级以下的对象包含引用。 结果克隆必须与原始实例具有相同的类型或是原始实例的兼容类型。

  代码实现如下:

 


 Code 
  public class Person:ICloneable 
  { 
  public int Age { get; set; } 
  public string Address { get; set; } 
  public Name Name { get; set; } 
  public object Clone() 
  { 
  Person tem = new Person(); 
  tem.Address = this.Address; 
  tem.Age = this.Age; 
  tem.Name = new Name(this.Name.FristName, this.Name.LastName); 
  return tem; 
  } 
  } 
  public class Name 
  { 
  public Name(string frisName, string lastName) 
  { 
  FristName = frisName; 
  LastName = lastName; 
  } 
  public string FristName { get; set; } 
  public string LastName { get; set; } 
  }

 

  大家可以看到,Person类继承了接口ICloneable并手动实现了其Clone方法,这是个简单的类,试想一下,如果你的类有成千上万个引用类型成员(当然太夸张,几十个还是有的),这是不是份很恐怖的劳力活?

  序列化/反序列化类实现

  不知道你有没有注意到DataSet对象,对于他提供的两个方法:

  DataSet.Clone 方法,复制 DataSet 的结构,包括所有 DataTable 架构、关系和约束。不要复制任何数据。

  新 DataSet,其架构与当前 DataSet 的架构相同,但是不包含任何数据。注意 如果已创建这些类的子类,则复本也将属于相同的子类。

  DataSet.Copy 方法复制该 DataSet 的结构和数据.

  新的 DataSet,具有与该 DataSet 相同的结构(表架构、关系和约束)和数据。注意如果已创建这些类的子类,则副本也将属于相同的子类。

  好像既不是浅拷贝,又不是深拷贝,是不是很失望?但是两个结合起来不是我们要的深拷贝吗?看看DataSet的实现,注意序列化接口:ISerializable

  序列化是将对象或对象图形转换为线性字节序列,以存储或传输到另一个位置的过程。

  反序列化是接受存储的信息并利用它重新创建对象的过程。

  通过 ISerializable 接口,类可以执行其自己的序列化行为。

  转换为线性字节序列后并利用其重新创建对象的过程是不是和我们的深拷贝的语意“逐位复制”很相像?

  代码实现如下:

 


      [Serializable] 
  public class Person : ICloneable 
  { 
  public int Age { get; set; } 
  public string Address { get; set; } 
  public Name Name { get; set; } 
  public object Clone() 
  { 
  using (MemoryStream ms = new MemoryStream(1000)) 
  { 
  object CloneObject; 
  BinaryFormatter bf = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.Clone)); 
  bf.Serialize(ms, this); 
  ms.Seek(0, SeekOrigin.Begin); 
  // 反序列化至另一个对象(即创建了一个原对象的深表副本) 
  CloneObject = bf.Deserialize(ms); 
  // 关闭流 
  ms.Close(); 
  return CloneObject; 
  } 
  } 
  } 
  [Serializable] 
  public class Name 
  { 
  public Name(string frisName, string lastName) 
  { 
  FristName = frisName; 
  LastName = lastName; 
  } 
  public string FristName { get; set; } 
  public string LastName { get; set; } 
  } 
  }

 

  注意:通过序列化和反序列化实现深拷贝,其和其字段类型必须标记为可序列化类型,既添加特性(Attribute)[Serializable]。

  通过反射实现

  通过序列化/反序列化方式我们能比较流畅的实现深拷贝,但是涉及到IO操作,托管的的环境中,IO操作比较消耗资源。 能不能有更优雅的解决方案。CreateInstance,对,利用反射特性。这个方法大家可以参考这篇博客:http://rubenhak.com/?p=70 文章反射类的Attribute,利用Activator.CreateInstance New一个类出来(有点像DataSet.Clone先获得架构),然后利用PropertyInfo的SetValue和GetValue方法,遍历的方式进行值填充。

  代码实现如下:

 


 public class Person 
  { 
  private List _friends = new List(); 
  public string Firstname { get; set; } 
  public string Lastname { get; set; } 
  [Cloneable(CloneableState.Exclude)] 
  [Cloneable(CloneableState.Include, "Friends")] 
  public List Friends { get { return _friends; } } 
  [Cloneable(CloneableState.Exclude)] 
  public PersonManager Manager { get; set; } 
  }

 

 

 

 

 

posted on 2013-12-12 21:43  RJ  阅读(218)  评论(0)    收藏  举报

导航