C# 实现对象的深浅拷贝的三种方式代码示例
面试的时候经常被问到c#对象的深浅拷贝实现以及区别,今天我们就来讲一下深拷贝和浅拷贝到底是什么。首先我们讲讲浅拷贝,浅拷贝就是将对象中的所有字段复制到新对象中去,浅拷贝对于值类型和引用类型有不同的影响。值类型的值被复制到副本中后,修改副本中的值不会影响原来对象的值。然而引用类型的属性被复制到副本中后,再修改副本中的值是会导致原来对象的值被修改的。
当然上面说到的引用类型不包含string。那么为什么引用类型修改副本里的值会造成原来对象的值变化而string类型就不会呢?首先我们需要知道string类型实际上是一个不可变的引用类型,这就意味着对字符串对象进行了初始化,该字符串对象就不能改变了。表面上我们用+=修改字符串的内容实际上是创建了一个新字符串,然后根据需要把旧字符串的内容复制到新字符串中。如果想深入了解string可以看看我的这篇文章 C# String和StringBuilder的区别及性能详解 - 实用工具_软件教程_代码大全_c#-有码挺好个人博客 (cisharp.com)
看了上面的浅拷贝,我们再来说说深拷贝,浅拷贝的使用还是需要斟酌的,否则处理不好就会出BUG。深拷贝对值类型和引用类型都没有区别对待。深拷贝也是将对象中的所有字段复制到新对象中去,但是对象无论是值类型还是引用类型都将被重新创建然后复制到副本对象去。对于副本对象的修改将不会影响到原对象,无论任何类型。下面是我整理的几种c#对象深拷贝的方式以及示例代码。
- 通过序列化,这种方式代码量最少;但是性能损耗较大
- 通过反射,这种方式相比于序列化;性能提升五倍左右
- 通过表达式树,这种方式相比于反序列化,性能提升二十倍左右
| 复制对象个数 | 1 | 10 | 100 | 1000 | 10000 | 100000 |
|---|---|---|---|---|---|---|
| 序列化(毫秒) | 6 | 7 | 29 | 189 | 1496 | 12969 |
| 反射(毫秒) | 5 | 5 | 10 | 36 | 242 | 2432 |
| 表达式树(毫秒) | 19 | 17 | 19 | 20 | 69 | 570 |
本文比较例子以及性能比较源码下载:DeepCopyExtension.zip,参考文章:Fast Deep Copy by Expression Trees (C#) - CodeProject、c# - 深度克隆对象 - stackoverflow (stackoverflow.com)
序列化实现深拷贝对象
通过序列化来实现对象深拷贝时,对象一定要打上[Serializable]特性,否则序列化时会抛出异常未标记为可序列化
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading.Tasks;
namespace TestConsole
{
public class Program
{
private static void Main(string[] args)
{
//定义源对象
var src_obj = new CiSharp();
//使用序列化深拷贝对象
var clo_obj = src_obj.DeepClone();
Console.ReadLine();
}
}
/// <summary>
/// 定义对象
/// </summary>
[Serializable]
public class CiSharp
{
public string Name { get; set; } = "cisharp.com";
}
/// <summary>
/// 定义对象深拷贝扩展方法
/// </summary>
public static class CloneExtend
{
public static T DeepClone<T>(this T obj)
{
using (var ms = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(ms, obj);
ms.Position = 0;
return (T)formatter.Deserialize(ms);
}
}
}
}
反射实现对象深拷贝
using System.Collections.Generic;
using System.Reflection;
using System;
using TestConsole.ArrayExtensions;
namespace TestConsole
{
public static class ObjectExtensions
{
private static readonly MethodInfo CloneMethod = typeof(Object).GetMethod("MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Instance);
public static bool IsPrimitive(this Type type)
{
if (type == typeof(String)) return true;
return (type.IsValueType & type.IsPrimitive);
}
public static Object DeepClone(this Object originalObject)
{
return InternalCopy(originalObject, new Dictionary<Object, Object>(new ReferenceEqualityComparer()));
}
private static Object InternalCopy(Object originalObject, IDictionary<Object, Object> visited)
{
if (originalObject == null) return null;
var typeToReflect = originalObject.GetType();
if (IsPrimitive(typeToReflect)) return originalObject;
if (visited.ContainsKey(originalObject)) return visited[originalObject];
if (typeof(Delegate).IsAssignableFrom(typeToReflect)) return null;
var cloneObject = CloneMethod.Invoke(originalObject, null);
if (typeToReflect.IsArray)
{
var arrayType = typeToReflect.GetElementType();
if (IsPrimitive(arrayType) == false)
{
Array clonedArray = (Array)cloneObject;
clonedArray.ForEach((array, indices) => array.SetValue(InternalCopy(clonedArray.GetValue(indices), visited), indices));
}
}
visited.Add(originalObject, cloneObject);
CopyFields(originalObject, visited, cloneObject, typeToReflect);
RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect);
return cloneObject;
}
private static void RecursiveCopyBaseTypePrivateFields(object originalObject, IDictionary<object, object> visited, object cloneObject, Type typeToReflect)
{
if (typeToReflect.BaseType != null)
{
RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect.BaseType);
CopyFields(originalObject, visited, cloneObject, typeToReflect.BaseType, BindingFlags.Instance | BindingFlags.NonPublic, info => info.IsPrivate);
}
}
private static void CopyFields(object originalObject, IDictionary<object, object> visited, object cloneObject, Type typeToReflect, BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy, Func<FieldInfo, bool> filter = null)
{
foreach (FieldInfo fieldInfo in typeToReflect.GetFields(bindingFlags))
{
if (filter != null && filter(fieldInfo) == false) continue;
if (IsPrimitive(fieldInfo.FieldType)) continue;
var originalFieldValue = fieldInfo.GetValue(originalObject);
var clonedFieldValue = InternalCopy(originalFieldValue, visited);
fieldInfo.SetValue(cloneObject, clonedFieldValue);
}
}
public static T Copy<T>(this T original)
{
return (T)Copy((Object)original);
}
}
public class ReferenceEqualityComparer : EqualityComparer<Object>
{
public override bool Equals(object x, object y)
{
return ReferenceEquals(x, y);
}
public override int GetHashCode(object obj)
{
if (obj == null) return 0;
return obj.GetHashCode();
}
}
namespace ArrayExtensions
{
public static class ArrayExtensions
{
public static void ForEach(this Array array, Action<Array, int[]> action)
{
if (array.LongLength == 0) return;
ArrayTraverse walker = new ArrayTraverse(array);
do action(array, walker.Position);
while (walker.Step());
}
}
internal class ArrayTraverse
{
public int[] Position;
private int[] maxLengths;
public ArrayTraverse(Array array)
{
maxLengths = new int[array.Rank];
for (int i = 0; i < array.Rank; ++i)
{
maxLengths[i] = array.GetLength(i) - 1;
}
Position = new int[array.Rank];
}
public bool Step()
{
for (int i = 0; i < Position.Length; ++i)
{
if (Position[i] < maxLengths[i])
{
Position[i]++;
for (int j = 0; j < i; j++)
{
Position[j] = 0;
}
return true;
}
}
return false;
}
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading.Tasks;
namespace TestConsole
{
public class Program
{
private static void Main(string[] args)
{
//定义源对象
var src_obj = new CiSharp();
//使用反射深拷贝对象
var clo_obj = src_obj.DeepClone();
Console.ReadLine();
}
}
/// <summary>
/// 定义对象
/// </summary>
public class CiSharp
{
public string Name { get; set; } = "cisharp.com";
}
}
表达式实现对象深拷贝
// Made by Frantisek Konopecky, Prague, 2014 - 2016
//
// Code comes under MIT licence - Can be used without
// limitations for both personal and commercial purposes.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace DeepCopyExtensions
{
/// <summary>
/// Superfast deep copier class, which uses Expression trees.
/// </summary>
public static class DeepCopyByExpressionTrees
{
private static readonly object IsStructTypeToDeepCopyDictionaryLocker = new object();
private static Dictionary<Type, bool> IsStructTypeToDeepCopyDictionary = new Dictionary<Type, bool>();
private static readonly object CompiledCopyFunctionsDictionaryLocker = new object();
private static Dictionary<Type, Func<object, Dictionary<object, object>, object>> CompiledCopyFunctionsDictionary =
new Dictionary<Type, Func<object, Dictionary<object, object>, object>>();
private static readonly Type ObjectType = typeof(Object);
private static readonly Type ObjectDictionaryType = typeof(Dictionary<object, object>);
/// <summary>
/// Creates a deep copy of an object.
/// </summary>
/// <typeparam name="T">Object type.</typeparam>
/// <param name="original">Object to copy.</param>
/// <param name="copiedReferencesDict">Dictionary of already copied objects (Keys: original objects, Values: their copies).</param>
/// <returns></returns>
public static T DeepClone<T>(this T original, Dictionary<object, object> copiedReferencesDict = null)
{
return (T)DeepCopyByExpressionTreeObj(original, false, copiedReferencesDict ?? new Dictionary<object, object>(new ReferenceEqualityComparer()));
}
private static object DeepCopyByExpressionTreeObj(object original, bool forceDeepCopy, Dictionary<object, object> copiedReferencesDict)
{
if (original == null)
{
return null;
}
var type = original.GetType();
if (IsDelegate(type))
{
return null;
}
if (!forceDeepCopy && !IsTypeToDeepCopy(type))
{
return original;
}
object alreadyCopiedObject;
if (copiedReferencesDict.TryGetValue(original, out alreadyCopiedObject))
{
return alreadyCopiedObject;
}
if (type == ObjectType)
{
return new object();
}
var compiledCopyFunction = GetOrCreateCompiledLambdaCopyFunction(type);
object copy = compiledCopyFunction(original, copiedReferencesDict);
return copy;
}
private static Func<object, Dictionary<object, object>, object> GetOrCreateCompiledLambdaCopyFunction(Type type)
{
// The following structure ensures that multiple threads can use the dictionary
// even while dictionary is locked and being updated by other thread.
// That is why we do not modify the old dictionary instance but
// we replace it with a new instance everytime.
Func<object, Dictionary<object, object>, object> compiledCopyFunction;
if (!CompiledCopyFunctionsDictionary.TryGetValue(type, out compiledCopyFunction))
{
lock (CompiledCopyFunctionsDictionaryLocker)
{
if (!CompiledCopyFunctionsDictionary.TryGetValue(type, out compiledCopyFunction))
{
var uncompiledCopyFunction = CreateCompiledLambdaCopyFunctionForType(type);
compiledCopyFunction = uncompiledCopyFunction.Compile();
var dictionaryCopy = CompiledCopyFunctionsDictionary.ToDictionary(pair => pair.Key, pair => pair.Value);
dictionaryCopy.Add(type, compiledCopyFunction);
CompiledCopyFunctionsDictionary = dictionaryCopy;
}
}
}
return compiledCopyFunction;
}
private static Expression<Func<object, Dictionary<object, object>, object>> CreateCompiledLambdaCopyFunctionForType(Type type)
{
ParameterExpression inputParameter;
ParameterExpression inputDictionary;
ParameterExpression outputVariable;
ParameterExpression boxingVariable;
LabelTarget endLabel;
List<ParameterExpression> variables;
List<Expression> expressions;
///// INITIALIZATION OF EXPRESSIONS AND VARIABLES
InitializeExpressions(type,
out inputParameter,
out inputDictionary,
out outputVariable,
out boxingVariable,
out endLabel,
out variables,
out expressions);
///// RETURN NULL IF ORIGINAL IS NULL
IfNullThenReturnNullExpression(inputParameter, endLabel, expressions);
///// MEMBERWISE CLONE ORIGINAL OBJECT
MemberwiseCloneInputToOutputExpression(type, inputParameter, outputVariable, expressions);
///// STORE COPIED OBJECT TO REFERENCES DICTIONARY
if (IsClassOtherThanString(type))
{
StoreReferencesIntoDictionaryExpression(inputParameter, inputDictionary, outputVariable, expressions);
}
///// COPY ALL NONVALUE OR NONPRIMITIVE FIELDS
FieldsCopyExpressions(type,
inputParameter,
inputDictionary,
outputVariable,
boxingVariable,
expressions);
///// COPY ELEMENTS OF ARRAY
if (IsArray(type) && IsTypeToDeepCopy(type.GetElementType()))
{
CreateArrayCopyLoopExpression(type,
inputParameter,
inputDictionary,
outputVariable,
variables,
expressions);
}
///// COMBINE ALL EXPRESSIONS INTO LAMBDA FUNCTION
var lambda = CombineAllIntoLambdaFunctionExpression(inputParameter, inputDictionary, outputVariable, endLabel, variables, expressions);
return lambda;
}
private static void InitializeExpressions(Type type,
out ParameterExpression inputParameter,
out ParameterExpression inputDictionary,
out ParameterExpression outputVariable,
out ParameterExpression boxingVariable,
out LabelTarget endLabel,
out List<ParameterExpression> variables,
out List<Expression> expressions)
{
inputParameter = Expression.Parameter(ObjectType);
inputDictionary = Expression.Parameter(ObjectDictionaryType);
outputVariable = Expression.Variable(type);
boxingVariable = Expression.Variable(ObjectType);
endLabel = Expression.Label();
variables = new List<ParameterExpression>();
expressions = new List<Expression>();
variables.Add(outputVariable);
variables.Add(boxingVariable);
}
private static void IfNullThenReturnNullExpression(ParameterExpression inputParameter, LabelTarget endLabel, List<Expression> expressions)
{
///// Intended code:
/////
///// if (input == null)
///// {
///// return null;
///// }
var ifNullThenReturnNullExpression =
Expression.IfThen(
Expression.Equal(
inputParameter,
Expression.Constant(null, ObjectType)),
Expression.Return(endLabel));
expressions.Add(ifNullThenReturnNullExpression);
}
private static void MemberwiseCloneInputToOutputExpression(
Type type,
ParameterExpression inputParameter,
ParameterExpression outputVariable,
List<Expression> expressions)
{
///// Intended code:
/////
///// var output = (<type>)input.MemberwiseClone();
var memberwiseCloneMethod = ObjectType.GetMethod("MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Instance);
var memberwiseCloneInputExpression =
Expression.Assign(
outputVariable,
Expression.Convert(
Expression.Call(
inputParameter,
memberwiseCloneMethod),
type));
expressions.Add(memberwiseCloneInputExpression);
}
private static void StoreReferencesIntoDictionaryExpression(ParameterExpression inputParameter,
ParameterExpression inputDictionary,
ParameterExpression outputVariable,
List<Expression> expressions)
{
///// Intended code:
