C#中4种深拷贝方法介绍
在C#中,对象的拷贝通常分为浅拷贝和深拷贝。浅拷贝只是简单地复制变量值,两个对象所引用的堆内存空间是相同的;深拷贝则是创建一个新的对象,并复制其中所有的属性,两个对象所引用的堆内存空间是不同的。深拷贝通常在需要复制对象并修改其属性的情况下使用,而浅拷贝则更适合在对对象的只读访问上使用。
下面介绍C#中4种常用的深拷贝方法。
1. 使用序列化
使用序列化方法,可以将对象以字节的形式进行转换并复制。首先创建一个MemoryStream,然后通过创建BinaryFormatter对象并将对象序列化到MemoryStream中,最后将MemoryStream中的字节数组反序列化为新的对象即可。
public static T DeepCopy<T>(T obj) {
using (var stream = new MemoryStream()) {
var formatter = new BinaryFormatter();
formatter.Serialize(stream, obj);
stream.Seek(0, SeekOrigin.Begin);
return (T)formatter.Deserialize(stream);
}
}
示例
我们定义一个Person类,其中包含一个String类型的属性Name和一个对象类型的属性Attribute。
[Serializable]
class Person
{
public string Name { get; set; }
public object Attribute { get; set; }
}
我们创建了一个Person的实例对象p1,并为其赋值Name为"张三",Attribute为一个int类型的值100。然后将p1拷贝到p2中,并修改p2的Name为"李四",输出p1和p2的Name和Attribute值,可以看到两个对象的Name值不同,而Attribute的值相同,说明使用序列化方法成功地进行了拷贝。
Person p1 = new Person {Name = "张三", Attribute = 100};
Person p2 = DeepCopy(p1);
p2.Name = "李四";
Console.WriteLine(p1.Name + " " + p1.Attribute);
Console.WriteLine(p2.Name + " " + p2.Attribute);
输出结果为:
张三 100
李四 100
2. 使用反射
使用反射方法,可以遍历对象中的所有字段及其属性,并将其复制到一个新的对象中。需要注意的是,使用反射方法需要对字段的属性进行判断,避免出现代码死循环或者拷贝的信息不完整等问题。
public static T DeepCopy<T>(T obj)
{
if (obj == null) return default(T);
Type type = obj.GetType();
if (type.IsValueType) return obj;
object result = Activator.CreateInstance(type);
FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (FieldInfo field in fields)
{
try
{
object fieldValue = field.GetValue(obj);
if (fieldValue != null)
{
if (!field.FieldType.IsValueType && field.FieldType != typeof(string))
{
object deepCopy = DeepCopy(fieldValue);
field.SetValue(result, deepCopy);
}
else
{
field.SetValue(result, fieldValue);
}
}
}
catch
{
continue;
}
}
return (T)result;
}
示例
我们定义一个Person类,其中包含一个String类型的属性Name和一个Person类型的属性Father。
class Person
{
public string Name { get; set; }
public Person Father { get; set; }
}
我们创建了一个Person的实例对象p1,为其赋值Name为"张三",Father为一个新的Person实例对象p2,为p2的Name赋值为"李四"。然后将p1拷贝到p3中,并修改p3的Name为"王五",Father的Name为"赵六",输出p1、p2、p3三个对象的Name和Father的Name属性值,可以看到三个对象的Name值和Father的Name值均不同,说明使用反射方法成功地进行了拷贝。
Person p1 = new Person {Name = "张三", Father = new Person {Name = "李四"}};
Person p2 = p1.Father;
Person p3 = DeepCopy(p1);
p3.Name = "王五";
p3.Father.Name = "赵六";
Console.WriteLine(p1.Name + " " + p1.Father.Name);
Console.WriteLine(p2.Name + " " + p2.Father.Name);
Console.WriteLine(p3.Name + " " + p3.Father.Name);
输出结果为:
张三 李四
李四 李四
王五 赵六
3. 使用泛型枚举
使用泛型枚举方法,可以对所有支持枚举类型的对象进行拷贝。通过遍历枚举类型,将每一个枚举元素赋值到新的对象中即可。
public static T DeepCopy<T>(T obj) where T : class
{
if (obj == null) return null;
Type type = obj.GetType();
if (type.IsValueType || type == typeof(string)) return obj;
object result = Activator.CreateInstance(type);
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo property in properties)
{
if (property.CanWrite)
{
Type propertyType = property.PropertyType;
if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition().Equals(typeof(List<>)))
{
object value = property.GetValue(obj, null);
Type[] genericArguments = property.PropertyType.GetGenericArguments();
Type listItemType = genericArguments[0];
Type listType = typeof(List<>).MakeGenericType(listItemType);
object list = Activator.CreateInstance(listType);
foreach (object listItem in (IEnumerable)value)
{
object listItemCopy = DeepCopy(listItem);
listType.GetMethod("Add").Invoke(list, new[] { listItemCopy });
}
property.SetValue(result, list, null);
}
else if (propertyType.IsClass && propertyType != typeof(string))
{
object value = property.GetValue(obj, null);
object valueCopy = DeepCopy(value);
property.SetValue(result, valueCopy, null);
}
else
{
object value = property.GetValue(obj, null);
property.SetValue(result, value, null);
}
}
}
return (T)result;
}
示例
我们定义一个Computer类,其中包含一个string类型的属性Name和一个List
class Computer
{
public string Name { get; set; }
public List<double> Prices { get; set; }
}
我们创建了一个Computer的实例对象c1,为其赋值Name为"联想电脑",Prices为{10000.0, 8000.0},然后将c1拷贝到c2中,并修改c2的Name为"华硕电脑",Prices为{6000.0, 4000.0},输出c1和c2的Name和Prices属性值,可以看到两个对象的Name和Prices值分别不同,说明使用泛型枚举方法成功地进行了拷贝。
Computer c1 = new Computer {Name = "联想电脑", Prices = new List<double> {10000.0, 8000.0}};
Computer c2 = DeepCopy(c1);
c2.Name = "华硕电脑";
c2.Prices[0] = 6000.0;
c2.Prices[1] = 4000.0;
Console.WriteLine(c1.Name + " " + string.Join(",", c1.Prices));
Console.WriteLine(c2.Name + " " + string.Join(",", c2.Prices));
输出结果为:
联想电脑 10000,8000
华硕电脑 6000,4000
4. 使用Expression
使用Expression方法可以创建一个新的表达式树,并通过编译新的表达式树来生成一个新的对象。需要注意的是,使用Expression方法只支持对公共属性的拷贝。
public static Func<T, T> DeepCopy<T>()
{
Type type = typeof(T);
ParameterExpression parameter = Expression.Parameter(type, "p");
List<MemberBinding> bindings = new List<MemberBinding>();
foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (!property.CanRead || !property.CanWrite) continue;
MemberExpression propertyAccess = Expression.Property(parameter, property);
bindings.Add(Expression.Bind(property, propertyAccess));
}
Expression memberInit = Expression.MemberInit(Expression.New(type), bindings);
Expression<Func<T, T>> lambda = Expression.Lambda<Func<T, T>>(memberInit, parameter);
return lambda.Compile();
}
示例
我们定义一个Student类,其中包含一个string类型的属性Name和一个int类型的属性Age。
class Student
{
public string Name { get; set; }
public int Age { get; set; }
}
我们创建了一个Student的实例对象s1,为其赋值Name为"张三",Age为20,然后将s1拷贝到s2中,并修改s2的Name为"李四",Age为22,输出s1和s2的Name和Age属性值,可以看到两个对象的Name和Age值均不同,说明使用Expression方法成功地进行了拷贝。
Student s1 = new Student {Name = "张三", Age = 20};
Func<Student, Student> deepCopyFunc = DeepCopy<Student>();
Student s2 = deepCopyFunc(s1);
s2.Name = "李四";
s2.Age = 22;
Console.WriteLine(s1.Name + " " + s1.Age);
Console.WriteLine(s2.Name + " " + s2.Age);
输出结果为:
张三 20
李四 22
总结
以上是C#中4种主要的深拷贝方法,分别是使用:
- 序列化方法
- 反射方法
- 泛型枚举方法
- Expression方法
每种方法都有自己的优缺点,在选择时需要考虑到是否需要复制私有字段、是否需要兼容所有类型、是否需要能够进行延迟编译等问题。在实际应用中,可以根据需求综合考虑,选择合适的方法进行深拷贝。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:C#中4种深拷贝方法介绍 - Python技术站