对于所有面向对象的语言,复制永远是一个容易引发讨论的题目,C#中也不例外。此类问题在面试中极其容易被问到,我们应该在了解浅拷贝和深拷贝基本概念的基础上,从设计的角度进一步考虑如何支持对象的拷贝。
在System.Object类中,有一个受保护的方法object.MemberwiseClone(),这个方法实现了对象的复制。事实上,它所实现的就是我们所称的浅拷贝。
深拷贝:指的是拷贝一个对象时,不仅仅把对象的引用进行复制,还把该对象引用的值也一起拷贝。
这样进行深拷贝后的拷贝对象就和源对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。
比如一个黄狗叫大黄,使用克隆术克隆另外一个黄狗叫小黄,这样大黄和小黄就相对独立了,他们不互相影响。在.NET中int,double以及结构体和枚举等。
int a=12;
int c=a;//进行了深拷贝
c=232 //不影响
浅拷贝:指的是拷贝一个对象时,仅仅拷贝对象的引用进行拷贝,但是拷贝对象和源对象还是引用同一份实体。
此时,其中一个对象的改变都会影响到另一个对象。
就像一个人改名了一样,他还是这个人,只不过名字变了而已。
public class YDog
{
public string Name { get; set; }
}
class Program
{
static void Main(string[] args)
{
YDog sourceP = new YDog() { Name = "大黄" };
YDog copyP = sourceP; // 浅拷贝
copyP.Name = "小黄"; // 拷贝对象改变Name值
// 结果都是"小黄",因为实现的是浅拷贝,一个对象的改变都会影响到另一个对象
Console.WriteLine("YDog.Name: [SourceP: {0}] [CopyP:{1}]", sourceP.Name, copyP.Name);
Console.Read();
}
}
所谓的浅拷贝,是指拷贝一个对象的时候,拷贝原始对象中所有的非静态值类型成员和所有的引用类型成员的引用。
换言之,新的对象和原始对象将共享所有引用类型成员的实际对象。而相对的,深拷贝是指不仅复制所有的非静态值类型成员,而且也复制所有引用类型成员的实际对象。深拷贝和浅拷贝的概念是递归的,也就是说当引用类型成员中包含另外一个引用类型成员时,拷贝的时候将对其内部成员实行同样的复制策略。
浅拷贝示意图如下所示:
深拷贝示意图如下图所示:
类型基类System.Object已经为所有类型都实现了浅拷贝,类型所要做的就是公开一个复制的接口,而通常的,这个接口会借由实现ICloneable接口来实现。ICLoneable只包含一个Clone方法。该方法既可以被实现为浅拷贝也可以被实现为深拷贝,具体如何取舍需要根据具体类型的需求来决定。下面的代码提供了一个深拷贝的简单示例:
using System;
namespace DeepCopy
{
class Program
{
static void Main(string[] args)
{
// 定义原始对象
DpCopy dc = new DpCopy();
dc._i = 10;
dc._a = new A();
// 定义深拷贝对象
DpCopy deepClone = (DpCopy)dc.Clone();
// 定义浅拷贝对象
DpCopy shadowclone = (DpCopy)dc.MemberwiseClone();
// 深拷贝的复制对象将拥有自己的引用类型成员对象
// 所以这里的赋值不会影响原始对象
deepClone._a._s = "我是深拷贝的A";
Console.WriteLine(dc);
Console.WriteLine(deepClone);
Console.WriteLine("\r\n");
// 浅拷贝的复制对象共享原始对象的引用类型成员对象
// 所以这里的赋值将影响原始对象
shadowclone._a._s = "我是浅拷贝的A";
Console.WriteLine(dc);
Console.WriteLine(shadowclone);
Console.ReadKey();
}
}
public class DpCopy : ICloneable
{
public int _i = 0;
public A _a = new A();
public object Clone()
{
// 实现深拷贝
DpCopy newDc = new DpCopy();
// 重新实例化一个引用类型变量
newDc._a = new A();
// 给新引用类型变量的成员值
newDc._a._s = _a._s;
newDc._i = _i;
return newDc;
}
// 实现浅拷贝
public new object MemberwiseClone()
{
return base.MemberwiseClone();
}
///
/// 重写类的ToString()方法
///
///
public override string ToString()
{
return "I的值为:" + _i.ToString() + ",A为:" + _a._s;
}
}
///
/// 包含一个引用成员的类型
///
public class A
{
public string _s = "我是原始A";
}
}
在上面的代码中,类型DpCopy通过ICLoneable接口的Clone方法提供了深拷贝,并且通过提供一个MemberwiseClone的公共方法提供了浅拷贝。DpCopy类型具有一个值类型成员和一个引用类型成员,引用类型成员在浅拷贝和深拷贝时将展现不同的特性,浅拷贝的原始对象和目标对象公用了一个引用类型成员对象,这在程序的执行结果中可以清楚地看到:
有的参考资料上说C#中的深拷贝通过ICloneable接口来实现。这句话并不正确。事实上任何名字的方法都可以用来实现深拷贝,并且没有任何语法来规定深拷贝只能通过Clone方法来实现。Clone这个名字只是一种习惯的称呼,而实现ICloneable只能带来一般接口的通用便利性,而并没有任何关于拷贝的特殊性。
一般可被继承的类型应该避免实现ICloneable接口,因为这样做将强制所有的子类型都需要实现ICloneable接口,否则将使类型的深拷贝不能覆盖子类的新成员。
实现深拷贝
1、新建一个对象,一个一个的重新赋值,麻烦一点
```csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ServiceTest
{
public class Program
{
static void Main(string[] args)
{
YDog Dog = new YDog() { Name = "大黄" };
YDog NewDog = new YDog();
NewDog.Name = Dog.Name;
Console.WriteLine($"Dog.Name:{Dog.Name},NewDog.Name:{NewDog.Name}");
Console.Read();
}
}
}
![在这里插入图片描述](https://img-blog.csdnimg.cn/8462880744b0438c973e26190f8ea46f.png)
2、利用反射实现深拷贝
```csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace ServiceTest
{
public class Program
{
static void Main(string[] args)
{
YDog Dog = new YDog() { Name = "大黄" };
YDog NewDog = (YDog)DeepCopy(Dog);
NewDog.Name = Dog.Name;
Console.WriteLine($"Dog.Name:{Dog.Name},NewDog.Name:{NewDog.Name}");
Console.Read();
}
/* 利用反射实现深拷贝*/
public static object DeepCopy(object _object)
{
Type T = _object.GetType();
object o = Activator.CreateInstance(T);
PropertyInfo[] PI = T.GetProperties();
for (int i = 0; i < PI.Length; i++)
{
PropertyInfo P = PI[i];
P.SetValue(o, P.GetValue(_object));
}
return o;
}
}
}
3、利用序列化和反序列化来实现,如下代码
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
namespace ServiceTest
{
public class Program
{
static void Main(string[] args)
{
YDog Dog = new YDog() { Name = "大黄" };
//YDog NewDog = (YDog)DeepCopy(Dog);
//NewDog.Name = Dog.Name;
// 序列化实现
YDog NewDog = (YDog)DeepCopy<YDog>(Dog);
Console.WriteLine($"Dog.Name:{Dog.Name},NewDog.Name:{NewDog.Name}");
Console.Read();
}
/* 利用反射实现深拷贝*/
public static object DeepCopy(object _object)
{
Type T = _object.GetType();
object o = Activator.CreateInstance(T);
PropertyInfo[] PI = T.GetProperties();
for (int i = 0; i < PI.Length; i++)
{
PropertyInfo P = PI[i];
P.SetValue(o, P.GetValue(_object));
}
return o;
}
// 利用XML序列化和反序列化实现
public static T DeepCopyWithXmlSerializer<T>(T obj)
{
object retval;
using (MemoryStream ms = new MemoryStream())
{
XmlSerializer xml = new XmlSerializer(typeof(T));
xml.Serialize(ms, obj);
ms.Seek(0, SeekOrigin.Begin);
retval = xml.Deserialize(ms);
ms.Close();
}
return (T)retval;
}
// 利用二进制序列化和反序列实现
public static T DeepCopyWithBinarySerialize<T>(T obj)
{
object retval;
using (MemoryStream ms = new MemoryStream())
{
BinaryFormatter bf = new BinaryFormatter();
// 序列化成流
bf.Serialize(ms, obj);
ms.Seek(0, SeekOrigin.Begin);
// 反序列化成对象
retval = bf.Deserialize(ms);
ms.Close();
}
return (T)retval;
}
public static T DeepCopy<T>(T obj)
{
// 序列化
string json= JsonConvert.SerializeObject(obj);
// 反序列化
return JsonConvert.DeserializeObject<T>(json);
}
}
}
二、总结
浅拷贝是指复制类型中的所有值类型成员,而只赋值引用类型成员的引用,并且使目标对象共享原对象的引用类型成员对象。深拷贝是指同时复制值类型成员和引用类型成员的对象。浅拷贝和深拷贝的概念都是递归的。System.Object中的MemberwiseClone已经实现了浅拷贝,但它是一个受保护的方法。无论深拷贝还是浅拷贝,都可以通过实现ICloneable接口的Clone方法来实现,可被继承的类型需要谨慎地实现ICloneable接口,因为这将导致所有的子类型都必须实现ICloneable接口。
在开发过程中经常会遇到比较排序的问题,比如说对集合数组的排序等情况,基本类型都提供了默认的比较算法,如string提供了按字母进行排序,而int整数则是根据整数大小进行排序.但是在引用类型中(具有多个字段),那么这个排序当然也是取决于我们特定的值。
该接口由其值可以排序或排序的类型实现,并提供强类型的比较方法以对泛型集合对象的成员进行排序,
例如数字可以大于第二个数字,一个字符串可以在另一个字符串之前以字母顺序出现。
他要求实现类型定义的一个方法,CompareTo(T)该方法指示当前实现在排序顺序中的位置是在同一个类型和第二个对象之前、之后还是与其相同。通常,不会直接从开发人员代码中调用方法。相反他由List.Sort()和Add等方法自动调用。
通常,提供Icomparable实现的类型还IEquatable实现接口。IEquatable接口Equals定义方法,该方法确定实现类型的实例的相等性。
CompareTo(T)方法的实现必须Int32返回具有以下三个值之一的,如下表所示。
CompareTo()方法返回值 | 含义 |
---|---|
小于零 | 此对象在排序顺序中位于CompareTo方法所指定的对象之前。 |
零 | 此当前实例在排序顺序中与CompareTo方法参数指定的对象出现在同一位置。 |
大于零 | 此当前实例位于排序顺序中由CompareTo方法自变量指定的对象之后。 |
class Student : IComparable
{
public string Name { get; set; }
public int Age { get; set; }
public int CompareTo(object obj)
{
if (!(obj is Student))
{
throw new ArgumentException("Compared Object is not of student");
}
Student student = obj as Student;
return Age.CompareTo(student.Age);
}
}
class Program
{
static void Main(string[] args)
{
ArrayList studentList = new ArrayList {
new Student{Name="a",Age=9 },
new Student{Name="a3",Age=7 },
new Student{Name="a1",Age=6 },
new Student{Name="a2",Age=10 },
};
studentList.Sort();
StudentComparable(studentList);
Console.ReadLine();
}
private static void StudentComparable(ArrayList studentList)
{
foreach (Student item in studentList)
{
Console.WriteLine("Name:{0},Age:{1}", item.Name, item.Age);
}
}
}
IComparable 接口的CompareTo方法一次只能对一个字段进行排序,因此无法对不同的属性进行排序。IComparer接口提供了Compare方法,该方法比较两个对象并返回一个值,该值指示一个对象小于,等于或大于另一个对象。实现IComparer接口的类必须提供比较两个对象的Compare方法。例如,您可以创建一个StudentComparer类,该类实现IComparer,并具有一个Compare方法,该方法按Name比较Student对象。然后,您可以将StudentComparer对象传递给Array.Sort方法,它可以使用该对象对Student对象的数组进行排序。
这里是引用
class StudentComparer : IComparer
{
public int Compare(object x, object y)
{
Student x1 = x as Student;
Student y1 = y as Student;
return x1.Name.CompareTo(y1.Name);
}
}
class Program
{
static void Main(string[] args)
{
ArrayList studentList = new ArrayList {
new Student{Name="a",Age=9 },
new Student{Name="a3",Age=7 },
new Student{Name="a1",Age=6 },
new Student{Name="a2",Age=10 },
};
studentList.Sort(new StudentComparer());
StudentComparable(studentList);
Console.ReadLine();
}
private static void StudentComparable(ArrayList studentList)
{
foreach (Student item in studentList)
{
Console.WriteLine("Name:{0},Age:{1}", item.Name, item.Age);
}
}
}
上述示例中我们将对象进行了多次的装箱和拆箱,那么此时我们可以将方法改为泛型的,泛型的出现也让我们避免了装箱和拆箱的资源浪费.
最终我们实现的代码片段如下:
IComparable
class Student : IComparable<Student>
{
public string Name { get; set; }
public int Age { get; set; }
public int CompareTo([AllowNull] Student other)
{
return Age.CompareTo(other.Age);
}
}
IComparer
class StudentComparer : IComparer<Student>
{
public int Compare([AllowNull] Student x, [AllowNull] Student y)
{
return x.Name.CompareTo(y.Name);
}
}
private void txtBuilding_EditValueChanged(object sender, EventArgs e)
{
if (this.txtBuilding.EditValue == null || this.txtBuilding.EditValue.ToString() == "NullText")
{
return;
}
TombColumbariumAreaInfo2 arear = (TombColumbariumAreaInfo2)txtBuilding.GetSelectedDataRow();
if (arear == null)
return;
string wheretag = string.Format("Buildingid={0}", arear.ID);
List<TombColumbariumCell2Info> listSections = CallerFactory<ITombColumbariumCell2Service>.Instance.Find2(wheretag, "order by CellCode");
var distinctSectionList = listSections.DistinctBy(x => x.CellCode).ToList().OrderBy(x => x.CellCode).ThenBy(x => x.CellCode);
//WHC.Framework.Commons.StringUtil.
ArrayList arrayList = new ArrayList();
//调用时的代码以下:
IComparer fileNameComparer = new FilesNameComparerClass();
//这样排序后的字符串就为按字符串中的数值排序了,为:
//aa1,aa2,aa10,aa100
foreach (TombColumbariumCell2Info ouInfo in distinctSectionList)
{
arrayList.Add(ouInfo.CellCode);
}
arrayList.Sort(fileNameComparer);
cbxSection.Properties.Items.Clear();
for (int i=0;i<arrayList.Count;i++)
{
cbxSection.Properties.Items.Add(arrayList[i]);
}
//string daID = this.txtBuriaDate.EditValue.ToString(); //是ookUpEdit.Properties.ValueMember的值
//string xm = this.txtBuriaDate.Text.Trim();
}
public class CellCodeCompare : IComparer
{
public int Compare(object x, object y)
{
return new CaseInsensitiveComparer().Compare(((TombColumbariumCell2Info)y).CellCode, ((TombColumbariumCell2Info)x).CellCode);
}
}
///
///主要用于文件名的比较。
///
public class FilesNameComparerClass : IComparer
{
// Calls CaseInsensitiveComparer.Compare with the parameters reversed.
///
///比较两个字符串,若是含用数字,则数字按数字的大小来比较。
///
///
///
///
int IComparer.Compare(Object x, Object y)
{
if (x == null || y == null)
throw new ArgumentException("Parameters can't be null");
string fileA = x as string;
string fileB = y as string;
char[] arr1 = fileA.ToCharArray();
char[] arr2 = fileB.ToCharArray();
int i = 0, j = 0;
while (i < arr1.Length && j < arr2.Length)
{
if (char.IsDigit(arr1[i]) && char.IsDigit(arr2[j]))
{
string s1 = "", s2 = "";
while (i < arr1.Length && char.IsDigit(arr1[i]))
{
s1 += arr1[i];
i++;
}
while (j < arr2.Length && char.IsDigit(arr2[j]))
{
s2 += arr2[j];
j++;
}
if (int.Parse(s1) > int.Parse(s2))
{
return 1;
}
if (int.Parse(s1) < int.Parse(s2))
{
return -1;
}
}
else
{
if (arr1[i] > arr2[j])
{
return 1;
}
if (arr1[i] < arr2[j])
{
return -1;
}
i++;
j++;
}
}
if (arr1.Length == arr2.Length)
{
return 0;
}
else
{
return arr1.Length > arr2.Length ? 1 : -1;
}
// return string.Compare( fileA, fileB );
// return( (new CaseInsensitiveComparer()).Compare( y, x ) );
}
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ComparableCar
{
class Car : IComparable
{
// Constant for maximum speed.
public const int MaxSpeed = 100;
// Car properties.
public int CurrentSpeed { get; set; }
public string PetName { get; set; }
public int CarID { get; set; }
// Is the car still operational?
private bool carIsDead;
// A car has-a radio.
private Radio theMusicBox = new Radio();
// Constructors.
public Car() { }
public Car( string name, int currSp, int id )
{
CurrentSpeed = currSp;
PetName = name;
CarID = id;
}
// Property to return the SortByPetName comparer.
public static IComparer SortByPetName
{ get { return (IComparer)new PetNameComparer(); } }
#region Methods
public void CrankTunes( bool state )
{
// Delegate request to inner object.
theMusicBox.TurnOn(state);
}
// See if Car has overheated.
// This time, throw an exception if the user speeds up beyond MaxSpeed.
public void Accelerate( int delta )
{
if (carIsDead)
Console.WriteLine("{0} is out of order...", PetName);
else
{
CurrentSpeed += delta;
if (CurrentSpeed >= MaxSpeed)
{
carIsDead = true;
CurrentSpeed = 0;
// We need to call the HelpLink property, thus we need
// to create a local variable before throwing the Exception object.
Exception ex =
new Exception(string.Format("{0} has overheated!", PetName));
ex.HelpLink = "http://www.CarsRUs.com";
// Stuff in custom data regarding the error.
ex.Data.Add("TimeStamp",
string.Format("The car exploded at {0}", DateTime.Now));
ex.Data.Add("Cause", "You have a lead foot.");
throw ex;
}
else
Console.WriteLine("=> CurrentSpeed = {0}", CurrentSpeed);
}
}
//int IComparable.CompareTo( object obj )
//{
// Car temp = obj as Car;
// if (temp != null)
// {
// if (this.CarID > temp.CarID)
// return 1;
// if (this.CarID < temp.CarID)
// return -1;
// else
// return 0;
// }
// else
// throw new ArgumentException("Parameter is not a Car!");
//}
int IComparable.CompareTo( object obj )
{
Car temp = obj as Car;
if (temp != null)
return this.CarID.CompareTo(temp.CarID);
else
throw new ArgumentException("Parameter is not a Car!");
}
}
#endregion
}
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ComparableCar
{
// This helper class is used to sort an array of Cars by pet name.
public class PetNameComparer : IComparer
{
// Test the pet name of each object.
int IComparer.Compare( object o1, object o2 )
{
Car t1 = o1 as Car;
Car t2 = o2 as Car;
if (t1 != null && t2 != null)
return String.Compare(t1.PetName, t2.PetName);
else
throw new ArgumentException("Parameter is not a Car!");
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ComparableCar
{
class Program
{
static void Main( string[] args )
{
Console.WriteLine("***** Fun with Object Sorting *****\n");
// Make an array of Car objects.
Car[] myAutos = new Car[5];
myAutos[0] = new Car("Rusty", 80, 1);
myAutos[1] = new Car("Mary", 40, 234);
myAutos[2] = new Car("Viper", 40, 34);
myAutos[3] = new Car("Mel", 40, 4);
myAutos[4] = new Car("Chucky", 40, 5);
// Display current array.
Console.WriteLine("Here is the unordered set of cars:");
foreach (Car c in myAutos)
Console.WriteLine("{0} {1}", c.CarID, c.PetName);
// Now, sort them using IComparable!
Array.Sort(myAutos);
Console.WriteLine();
// Display sorted array.
Console.WriteLine("Here is the ordered set of cars:");
foreach (Car c in myAutos)
Console.WriteLine("{0} {1}", c.CarID, c.PetName);
Console.WriteLine();
// Dump sorted array.
// Now sort by pet name.
// Array.Sort(myAutos, new PetNameComparer());
// Sorting by pet name made a bit cleaner.
Array.Sort(myAutos, Car.SortByPetName);
Console.WriteLine("Ordering by pet name:");
foreach (Car c in myAutos)
Console.WriteLine("{0} {1}", c.CarID, c.PetName);
Console.ReadLine();
}
}
}