using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; namespace Masuit.Tools { /// /// /// public static class DeepCopyExt { private static readonly object IsStructTypeToDeepCopyDictionaryLocker = new object(); private static Dictionary _isStructTypeToDeepCopyDictionary = new Dictionary(); private static readonly object CompiledCopyFunctionsDictionaryLocker = new object(); private static Dictionary, object>> _compiledCopyFunctionsDictionary = new Dictionary, object>>(); private static readonly Type ObjectType = typeof(object); private static readonly Type ObjectDictionaryType = typeof(Dictionary); /// /// 深克隆 /// /// 原始对象 /// 需要被引用传递的对象(Keys: 原始对象, Values: 副本对象). /// public static T DeepClone(this T original, Dictionary copiedReferencesDict = null) { return (T)DeepCopyObj(original, false, copiedReferencesDict ?? new Dictionary(new ReferenceEqualityComparer())); } private static object DeepCopyObj(object original, bool forceDeepCopy, Dictionary copiedReferencesDict) { if (original == null) { return null; } var type = original.GetType(); if (IsDelegate(type)) { return null; } if (!forceDeepCopy && !IsTypeToDeepCopy(type)) { return original; } if (copiedReferencesDict.TryGetValue(original, out var alreadyCopiedObject)) { return alreadyCopiedObject; } if (type == ObjectType) { return new object(); } var compiledCopyFunction = GetOrCreateCompiledLambdaCopyFunction(type); object copy = compiledCopyFunction(original, copiedReferencesDict); return copy; } private static Func, object> GetOrCreateCompiledLambdaCopyFunction(Type type) { if (!_compiledCopyFunctionsDictionary.TryGetValue(type, out var 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, object>> CreateCompiledLambdaCopyFunctionForType(Type type) { InitializeExpressions(type, out var inputParameter, out var inputDictionary, out var outputVariable, out var boxingVariable, out var endLabel, out var variables, out var expressions); IfNullThenReturnNullExpression(inputParameter, endLabel, expressions); MemberwiseCloneInputToOutputExpression(type, inputParameter, outputVariable, expressions); if (IsClassOtherThanString(type)) { StoreReferencesIntoDictionaryExpression(inputParameter, inputDictionary, outputVariable, expressions); } FieldsCopyExpressions(type, inputParameter, inputDictionary, outputVariable, boxingVariable, expressions); if (IsArray(type) && IsTypeToDeepCopy(type.GetElementType())) { CreateArrayCopyLoopExpression(type, inputParameter, inputDictionary, outputVariable, variables, expressions); } 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 variables, out List expressions) { inputParameter = Expression.Parameter(ObjectType); inputDictionary = Expression.Parameter(ObjectDictionaryType); outputVariable = Expression.Variable(type); boxingVariable = Expression.Variable(ObjectType); endLabel = Expression.Label(); variables = new List(); expressions = new List(); variables.Add(outputVariable); variables.Add(boxingVariable); } private static void IfNullThenReturnNullExpression(ParameterExpression inputParameter, LabelTarget endLabel, List expressions) { 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 expressions) { 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 expressions) { var storeReferencesExpression = Expression.Assign(Expression.Property(inputDictionary, ObjectDictionaryType.GetProperty("Item"), inputParameter), Expression.Convert(outputVariable, ObjectType)); expressions.Add(storeReferencesExpression); } private static Expression, object>> CombineAllIntoLambdaFunctionExpression(ParameterExpression inputParameter, ParameterExpression inputDictionary, ParameterExpression outputVariable, LabelTarget endLabel, List variables, List expressions) { expressions.Add(Expression.Label(endLabel)); expressions.Add(Expression.Convert(outputVariable, ObjectType)); var finalBody = Expression.Block(variables, expressions); var lambda = Expression.Lambda, object>>(finalBody, inputParameter, inputDictionary); return lambda; } private static void CreateArrayCopyLoopExpression(Type type, ParameterExpression inputParameter, ParameterExpression inputDictionary, ParameterExpression outputVariable, List variables, List expressions) { var rank = type.GetArrayRank(); var indices = GenerateIndices(rank); variables.AddRange(indices); var elementType = type.GetElementType(); var assignExpression = ArrayFieldToArrayFieldAssignExpression(inputParameter, inputDictionary, outputVariable, elementType, type, indices); Expression forExpression = assignExpression; for (int dimension = 0; dimension < rank; dimension++) { var indexVariable = indices[dimension]; forExpression = LoopIntoLoopExpression(inputParameter, indexVariable, forExpression, dimension); } expressions.Add(forExpression); } private static List GenerateIndices(int arrayRank) { var indices = new List(); for (int i = 0; i < arrayRank; i++) { var indexVariable = Expression.Variable(typeof(int)); indices.Add(indexVariable); } return indices; } private static BinaryExpression ArrayFieldToArrayFieldAssignExpression(ParameterExpression inputParameter, ParameterExpression inputDictionary, ParameterExpression outputVariable, Type elementType, Type arrayType, List indices) { var indexTo = Expression.ArrayAccess(outputVariable, indices); var indexFrom = Expression.ArrayIndex(Expression.Convert(inputParameter, arrayType), indices); var forceDeepCopy = elementType != ObjectType; var rightSide = Expression.Convert(Expression.Call(DeepCopyByExpressionTreeObjMethod, Expression.Convert(indexFrom, ObjectType), Expression.Constant(forceDeepCopy, typeof(bool)), inputDictionary), elementType); var assignExpression = Expression.Assign(indexTo, rightSide); return assignExpression; } private static BlockExpression LoopIntoLoopExpression(ParameterExpression inputParameter, ParameterExpression indexVariable, Expression loopToEncapsulate, int dimension) { var lengthVariable = Expression.Variable(typeof(int)); var endLabelForThisLoop = Expression.Label(); var newLoop = Expression.Loop(Expression.Block(new ParameterExpression[0], Expression.IfThen(Expression.GreaterThanOrEqual(indexVariable, lengthVariable), Expression.Break(endLabelForThisLoop)), loopToEncapsulate, Expression.PostIncrementAssign(indexVariable)), endLabelForThisLoop); var lengthAssignment = GetLengthForDimensionExpression(lengthVariable, inputParameter, dimension); var indexAssignment = Expression.Assign(indexVariable, Expression.Constant(0)); return Expression.Block(new[] { lengthVariable }, lengthAssignment, indexAssignment, newLoop); } private static BinaryExpression GetLengthForDimensionExpression(ParameterExpression lengthVariable, ParameterExpression inputParameter, int i) { var getLengthMethod = typeof(Array).GetMethod("GetLength", BindingFlags.Public | BindingFlags.Instance); var dimensionConstant = Expression.Constant(i); return Expression.Assign(lengthVariable, Expression.Call(Expression.Convert(inputParameter, typeof(Array)), getLengthMethod, new[] { dimensionConstant })); } private static void FieldsCopyExpressions(Type type, ParameterExpression inputParameter, ParameterExpression inputDictionary, ParameterExpression outputVariable, ParameterExpression boxingVariable, List expressions) { var fields = GetAllRelevantFields(type); var readonlyFields = fields.Where(f => f.IsInitOnly).ToList(); var writableFields = fields.Where(f => !f.IsInitOnly).ToList(); bool shouldUseBoxing = readonlyFields.Any(); if (shouldUseBoxing) { var boxingExpression = Expression.Assign(boxingVariable, Expression.Convert(outputVariable, ObjectType)); expressions.Add(boxingExpression); } foreach (var field in readonlyFields) { if (IsDelegate(field.FieldType)) { ReadonlyFieldToNullExpression(field, boxingVariable, expressions); } else { ReadonlyFieldCopyExpression(type, field, inputParameter, inputDictionary, boxingVariable, expressions); } } if (shouldUseBoxing) { var unboxingExpression = Expression.Assign(outputVariable, Expression.Convert(boxingVariable, type)); expressions.Add(unboxingExpression); } foreach (var field in writableFields) { if (IsDelegate(field.FieldType)) { WritableFieldToNullExpression(field, outputVariable, expressions); } else { WritableFieldCopyExpression(type, field, inputParameter, inputDictionary, outputVariable, expressions); } } } private static FieldInfo[] GetAllRelevantFields(Type type, bool forceAllFields = false) { var fieldsList = new List(); var typeCache = type; while (typeCache != null) { fieldsList.AddRange(typeCache.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy).Where(field => forceAllFields || IsTypeToDeepCopy(field.FieldType))); typeCache = typeCache.BaseType; } return fieldsList.ToArray(); } private static FieldInfo[] GetAllFields(Type type) { return GetAllRelevantFields(type, true); } private static readonly Type FieldInfoType = typeof(FieldInfo); private static readonly MethodInfo SetValueMethod = FieldInfoType.GetMethod("SetValue", new[] { ObjectType, ObjectType }); private static void ReadonlyFieldToNullExpression(FieldInfo field, ParameterExpression boxingVariable, List expressions) { var fieldToNullExpression = Expression.Call(Expression.Constant(field), SetValueMethod, boxingVariable, Expression.Constant(null, field.FieldType)); expressions.Add(fieldToNullExpression); } private static readonly Type ThisType = typeof(DeepCopyExt); private static readonly MethodInfo DeepCopyByExpressionTreeObjMethod = ThisType.GetMethod("DeepCopyByExpressionTreeObj", BindingFlags.NonPublic | BindingFlags.Static); private static void ReadonlyFieldCopyExpression(Type type, FieldInfo field, ParameterExpression inputParameter, ParameterExpression inputDictionary, ParameterExpression boxingVariable, List expressions) { var fieldFrom = Expression.Field(Expression.Convert(inputParameter, type), field); var forceDeepCopy = field.FieldType != ObjectType; var fieldDeepCopyExpression = Expression.Call(Expression.Constant(field, FieldInfoType), SetValueMethod, boxingVariable, Expression.Call(DeepCopyByExpressionTreeObjMethod, Expression.Convert(fieldFrom, ObjectType), Expression.Constant(forceDeepCopy, typeof(bool)), inputDictionary)); expressions.Add(fieldDeepCopyExpression); } private static void WritableFieldToNullExpression(FieldInfo field, ParameterExpression outputVariable, List expressions) { var fieldTo = Expression.Field(outputVariable, field); var fieldToNullExpression = Expression.Assign(fieldTo, Expression.Constant(null, field.FieldType)); expressions.Add(fieldToNullExpression); } private static void WritableFieldCopyExpression(Type type, FieldInfo field, ParameterExpression inputParameter, ParameterExpression inputDictionary, ParameterExpression outputVariable, List expressions) { var fieldFrom = Expression.Field(Expression.Convert(inputParameter, type), field); var fieldType = field.FieldType; var fieldTo = Expression.Field(outputVariable, field); var forceDeepCopy = field.FieldType != ObjectType; var fieldDeepCopyExpression = Expression.Assign(fieldTo, Expression.Convert(Expression.Call(DeepCopyByExpressionTreeObjMethod, Expression.Convert(fieldFrom, ObjectType), Expression.Constant(forceDeepCopy, typeof(bool)), inputDictionary), fieldType)); expressions.Add(fieldDeepCopyExpression); } private static bool IsArray(Type type) { return type.IsArray; } private static bool IsDelegate(Type type) { return typeof(Delegate).IsAssignableFrom(type); } private static bool IsTypeToDeepCopy(Type type) { return IsClassOtherThanString(type) || IsStructWhichNeedsDeepCopy(type); } private static bool IsClassOtherThanString(Type type) { return !type.IsValueType && type != typeof(string); } private static bool IsStructWhichNeedsDeepCopy(Type type) { if (!_isStructTypeToDeepCopyDictionary.TryGetValue(type, out var isStructTypeToDeepCopy)) { lock (IsStructTypeToDeepCopyDictionaryLocker) { if (!_isStructTypeToDeepCopyDictionary.TryGetValue(type, out isStructTypeToDeepCopy)) { isStructTypeToDeepCopy = IsStructWhichNeedsDeepCopy_NoDictionaryUsed(type); var newDictionary = _isStructTypeToDeepCopyDictionary.ToDictionary(pair => pair.Key, pair => pair.Value); newDictionary[type] = isStructTypeToDeepCopy; _isStructTypeToDeepCopyDictionary = newDictionary; } } } return isStructTypeToDeepCopy; } private static bool IsStructWhichNeedsDeepCopy_NoDictionaryUsed(Type type) { return IsStructOtherThanBasicValueTypes(type) && HasInItsHierarchyFieldsWithClasses(type); } private static bool IsStructOtherThanBasicValueTypes(Type type) { return type.IsValueType && !type.IsPrimitive && !type.IsEnum && type != typeof(decimal); } private static bool HasInItsHierarchyFieldsWithClasses(Type type, HashSet alreadyCheckedTypes = null) { alreadyCheckedTypes ??= new HashSet(); alreadyCheckedTypes.Add(type); var allFields = GetAllFields(type); var allFieldTypes = allFields.Select(f => f.FieldType).Distinct().ToList(); var hasFieldsWithClasses = allFieldTypes.Any(IsClassOtherThanString); if (hasFieldsWithClasses) { return true; } var notBasicStructsTypes = allFieldTypes.Where(IsStructOtherThanBasicValueTypes).ToList(); var typesToCheck = notBasicStructsTypes.Where(t => !alreadyCheckedTypes.Contains(t)).ToList(); return typesToCheck.Any(typeToCheck => HasInItsHierarchyFieldsWithClasses(typeToCheck, alreadyCheckedTypes)); } public class ReferenceEqualityComparer : EqualityComparer { 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(); } } } }