using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; namespace MAX.Core.Helpers; public class CopyIgnoreAttribute : Attribute { } /// /// 拷贝帮助 /// public class CopyHelper { /// /// 反射深拷贝 /// /// /// /// public static T DeepCopy(T obj) { return (T)DeepCopy((Object)obj, null); } public static T DeepCopy(T obj, Func filter = null) { return (T)DeepCopy((Object)obj, filter); } public static Object DeepCopy(Object originalObject, Func filter = null) { return InternalCopy( originalObject, new Dictionary(new ReferenceEqualityComparer()), filter ); } private static readonly MethodInfo CloneMethod = typeof(Object).GetMethod( "MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Instance ); public static bool IsPrimitive(Type type) { if (type == typeof(String)) { return true; } return type.IsValueType & type.IsPrimitive; } private static Object InternalCopy( Object originalObject, IDictionary visited, Func filter = null ) { if (originalObject == null) { return null; } Type typeToReflect = originalObject.GetType(); if (IsPrimitive(typeToReflect)) { return originalObject; } if (visited.ContainsKey(originalObject)) { return visited[originalObject]; } if (typeof(Delegate).IsAssignableFrom(typeToReflect)) { return null; } object cloneObject = CloneMethod.Invoke(originalObject, null); if (typeToReflect.IsArray) { Type arrayType = typeToReflect.GetElementType(); if (IsPrimitive(arrayType) == false) { Array clonedArray = (Array)cloneObject; clonedArray.ForEach( (array, indices) => array.SetValue( InternalCopy(clonedArray.GetValue(indices), visited, filter), indices ) ); } } visited.Add(originalObject, cloneObject); CopyFields(originalObject, visited, cloneObject, typeToReflect, filter: filter); RecursiveCopyBaseTypePrivateFields( originalObject, visited, cloneObject, typeToReflect, filter ); return cloneObject; } private static void RecursiveCopyBaseTypePrivateFields( object originalObject, IDictionary visited, object cloneObject, Type typeToReflect, Func filter = null ) { 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 visited, object cloneObject, Type typeToReflect, BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy, Func filter = null ) { foreach (FieldInfo fieldInfo in typeToReflect.GetFields(bindingFlags)) { if (filter != null && filter(fieldInfo) == false) { continue; } if (IsPrimitive(fieldInfo.FieldType)) { continue; } object originalFieldValue = fieldInfo.GetValue(originalObject); object clonedFieldValue = InternalCopy(originalFieldValue, visited, filter: filter); fieldInfo.SetValue(cloneObject, clonedFieldValue); } } } /// /// Superfast deep copier class, which uses Expression trees. /// public static class DeepCopyByExpressionTrees { private static readonly object IsStructTypeToDeepCopyDictionaryLocker = new(); private static Dictionary IsStructTypeToDeepCopyDictionary = []; private static readonly object CompiledCopyFunctionsDictionaryLocker = new(); private static Dictionary< Type, Func, object> > CompiledCopyFunctionsDictionary = []; private static readonly Type ObjectType = typeof(Object); private static readonly Type ObjectDictionaryType = typeof(Dictionary); /// /// Creates a deep copy of an object. /// /// Object type. /// Object to copy. /// Dictionary of already copied objects (Keys: original objects, Values: their copies). /// public static T DeepCopyByExpressionTree( this T original, Dictionary copiedReferencesDict = null ) { return (T)DeepCopyByExpressionTreeObj( original, false, copiedReferencesDict ?? new Dictionary(new ReferenceEqualityComparer()) ); } private static object DeepCopyByExpressionTreeObj( object original, bool forceDeepCopy, Dictionary copiedReferencesDict ) { if (original == null) { return null; } Type type = original.GetType(); if (IsDelegate(type)) { return null; } if (!forceDeepCopy && !IsTypeToDeepCopy(type)) { return original; } if (copiedReferencesDict.TryGetValue(original, out object alreadyCopiedObject)) { return alreadyCopiedObject; } if (type == ObjectType) { return new object(); } Func, object> compiledCopyFunction = GetOrCreateCompiledLambdaCopyFunction(type); object copy = compiledCopyFunction(original, copiedReferencesDict); return copy; } private static Func< object, Dictionary, 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. if (!CompiledCopyFunctionsDictionary.TryGetValue(type, out Func, object> compiledCopyFunction)) { lock (CompiledCopyFunctionsDictionaryLocker) { if ( !CompiledCopyFunctionsDictionary.TryGetValue(type, out compiledCopyFunction) ) { Expression, object>> uncompiledCopyFunction = CreateCompiledLambdaCopyFunctionForType(type); compiledCopyFunction = uncompiledCopyFunction.Compile(); Dictionary, object>> dictionaryCopy = CompiledCopyFunctionsDictionary.ToDictionary( pair => pair.Key, pair => pair.Value ); dictionaryCopy.Add(type, compiledCopyFunction); CompiledCopyFunctionsDictionary = dictionaryCopy; } } } return compiledCopyFunction; } private static Expression< Func, object> > CreateCompiledLambdaCopyFunctionForType(Type type) { ///// INITIALIZATION OF EXPRESSIONS AND VARIABLES InitializeExpressions( type, out ParameterExpression inputParameter, out ParameterExpression inputDictionary, out ParameterExpression outputVariable, out ParameterExpression boxingVariable, out LabelTarget endLabel, out List variables, out List 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 Expression, object>> 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 = []; expressions = []; variables.Add(outputVariable); variables.Add(boxingVariable); } private static void IfNullThenReturnNullExpression( ParameterExpression inputParameter, LabelTarget endLabel, List expressions ) { ///// Intended code: ///// ///// if (input == null) ///// { ///// return null; ///// } ConditionalExpression 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 ) { ///// Intended code: ///// ///// var output = ()input.MemberwiseClone(); MethodInfo memberwiseCloneMethod = ObjectType.GetMethod( "MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Instance ); BinaryExpression 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 ) { ///// Intended code: ///// ///// inputDictionary[(Object)input] = (Object)output; BinaryExpression storeReferencesExpression = Expression.Assign( Expression.Property( inputDictionary, ObjectDictionaryType.GetProperty("Item"), inputParameter ), Expression.Convert(outputVariable, ObjectType) ); expressions.Add(storeReferencesExpression); } private static Expression< Func, 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)); BlockExpression finalBody = Expression.Block(variables, expressions); Expression, object>> 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 ) { ///// Intended code: ///// ///// int i1, i2, ..., in; ///// ///// int length1 = inputarray.GetLength(0); ///// i1 = 0; ///// while (true) ///// { ///// if (i1 >= length1) ///// { ///// goto ENDLABELFORLOOP1; ///// } ///// int length2 = inputarray.GetLength(1); ///// i2 = 0; ///// while (true) ///// { ///// if (i2 >= length2) ///// { ///// goto ENDLABELFORLOOP2; ///// } ///// ... ///// ... ///// ... ///// int lengthn = inputarray.GetLength(n); ///// in = 0; ///// while (true) ///// { ///// if (in >= lengthn) ///// { ///// goto ENDLABELFORLOOPn; ///// } ///// outputarray[i1, i2, ..., in] ///// = ()DeepCopyByExpressionTreeObj( ///// (Object)inputarray[i1, i2, ..., in]) ///// in++; ///// } ///// ENDLABELFORLOOPn: ///// ... ///// ... ///// ... ///// i2++; ///// } ///// ENDLABELFORLOOP2: ///// i1++; ///// } ///// ENDLABELFORLOOP1: int rank = type.GetArrayRank(); List indices = GenerateIndices(rank); variables.AddRange(indices); Type elementType = type.GetElementType(); BinaryExpression assignExpression = ArrayFieldToArrayFieldAssignExpression( inputParameter, inputDictionary, outputVariable, elementType, type, indices ); Expression forExpression = assignExpression; for (int dimension = 0; dimension < rank; dimension++) { ParameterExpression indexVariable = indices[dimension]; forExpression = LoopIntoLoopExpression( inputParameter, indexVariable, forExpression, dimension ); } expressions.Add(forExpression); } private static List GenerateIndices(int arrayRank) { ///// Intended code: ///// ///// int i1, i2, ..., in; List indices = []; for (int i = 0; i < arrayRank; i++) { ParameterExpression indexVariable = Expression.Variable(typeof(Int32)); indices.Add(indexVariable); } return indices; } private static BinaryExpression ArrayFieldToArrayFieldAssignExpression( ParameterExpression inputParameter, ParameterExpression inputDictionary, ParameterExpression outputVariable, Type elementType, Type arrayType, List indices ) { ///// Intended code: ///// ///// outputarray[i1, i2, ..., in] ///// = ()DeepCopyByExpressionTreeObj( ///// (Object)inputarray[i1, i2, ..., in]); IndexExpression indexTo = Expression.ArrayAccess(outputVariable, indices); MethodCallExpression indexFrom = Expression.ArrayIndex( Expression.Convert(inputParameter, arrayType), indices ); bool forceDeepCopy = elementType != ObjectType; UnaryExpression rightSide = Expression.Convert( Expression.Call( DeepCopyByExpressionTreeObjMethod, Expression.Convert(indexFrom, ObjectType), Expression.Constant(forceDeepCopy, typeof(Boolean)), inputDictionary ), elementType ); BinaryExpression assignExpression = Expression.Assign(indexTo, rightSide); return assignExpression; } private static BlockExpression LoopIntoLoopExpression( ParameterExpression inputParameter, ParameterExpression indexVariable, Expression loopToEncapsulate, int dimension ) { ///// Intended code: ///// ///// int length = inputarray.GetLength(dimension); ///// i = 0; ///// while (true) ///// { ///// if (i >= length) ///// { ///// goto ENDLABELFORLOOP; ///// } ///// loopToEncapsulate; ///// i++; ///// } ///// ENDLABELFORLOOP: ParameterExpression lengthVariable = Expression.Variable(typeof(Int32)); LabelTarget endLabelForThisLoop = Expression.Label(); LoopExpression newLoop = Expression.Loop( Expression.Block( new ParameterExpression[0], Expression.IfThen( Expression.GreaterThanOrEqual(indexVariable, lengthVariable), Expression.Break(endLabelForThisLoop) ), loopToEncapsulate, Expression.PostIncrementAssign(indexVariable) ), endLabelForThisLoop ); BinaryExpression lengthAssignment = GetLengthForDimensionExpression( lengthVariable, inputParameter, dimension ); BinaryExpression 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 ) { ///// Intended code: ///// ///// length = ((Array)input).GetLength(i); MethodInfo getLengthMethod = typeof(Array).GetMethod( "GetLength", BindingFlags.Public | BindingFlags.Instance ); ConstantExpression 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 ) { FieldInfo[] fields = GetAllRelevantFields(type); List readonlyFields = fields.Where(f => f.IsInitOnly).ToList(); List writableFields = fields.Where(f => !f.IsInitOnly).ToList(); ///// READONLY FIELDS COPY (with boxing) bool shouldUseBoxing = readonlyFields.Any(); if (shouldUseBoxing) { BinaryExpression boxingExpression = Expression.Assign( boxingVariable, Expression.Convert(outputVariable, ObjectType) ); expressions.Add(boxingExpression); } foreach (FieldInfo field in readonlyFields) { if (IsDelegate(field.FieldType)) { ReadonlyFieldToNullExpression(field, boxingVariable, expressions); } else { ReadonlyFieldCopyExpression( type, field, inputParameter, inputDictionary, boxingVariable, expressions ); } } if (shouldUseBoxing) { BinaryExpression unboxingExpression = Expression.Assign( outputVariable, Expression.Convert(boxingVariable, type) ); expressions.Add(unboxingExpression); } ///// NOT-READONLY FIELDS COPY foreach (FieldInfo 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) { List fieldsList = []; Type 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, forceAllFields: 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 ) { // This option must be implemented by Reflection because of the following: // https://visualstudio.uservoice.com/forums/121579-visual-studio-2015/suggestions/2727812-allow-expression-assign-to-set-readonly-struct-f ///// Intended code: ///// ///// fieldInfo.SetValue(boxing, null); MethodCallExpression fieldToNullExpression = Expression.Call( Expression.Constant(field), SetValueMethod, boxingVariable, Expression.Constant(null, field.FieldType) ); expressions.Add(fieldToNullExpression); } private static readonly Type ThisType = typeof(DeepCopyByExpressionTrees); 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 ) { // This option must be implemented by Reflection (SetValueMethod) because of the following: // https://visualstudio.uservoice.com/forums/121579-visual-studio-2015/suggestions/2727812-allow-expression-assign-to-set-readonly-struct-f ///// Intended code: ///// ///// fieldInfo.SetValue(boxing, DeepCopyByExpressionTreeObj((Object)(()input).)) MemberExpression fieldFrom = Expression.Field(Expression.Convert(inputParameter, type), field); bool forceDeepCopy = field.FieldType != ObjectType; MethodCallExpression fieldDeepCopyExpression = Expression.Call( Expression.Constant(field, FieldInfoType), SetValueMethod, boxingVariable, Expression.Call( DeepCopyByExpressionTreeObjMethod, Expression.Convert(fieldFrom, ObjectType), Expression.Constant(forceDeepCopy, typeof(Boolean)), inputDictionary ) ); expressions.Add(fieldDeepCopyExpression); } private static void WritableFieldToNullExpression( FieldInfo field, ParameterExpression outputVariable, List expressions ) { ///// Intended code: ///// ///// output. = ()null; MemberExpression fieldTo = Expression.Field(outputVariable, field); BinaryExpression 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 ) { ///// Intended code: ///// ///// output. = ()DeepCopyByExpressionTreeObj((Object)(()input).); MemberExpression fieldFrom = Expression.Field(Expression.Convert(inputParameter, type), field); Type fieldType = field.FieldType; MemberExpression fieldTo = Expression.Field(outputVariable, field); bool forceDeepCopy = field.FieldType != ObjectType; BinaryExpression fieldDeepCopyExpression = Expression.Assign( fieldTo, Expression.Convert( Expression.Call( DeepCopyByExpressionTreeObjMethod, Expression.Convert(fieldFrom, ObjectType), Expression.Constant(forceDeepCopy, typeof(Boolean)), 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) { // 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. if (!IsStructTypeToDeepCopyDictionary.TryGetValue(type, out bool isStructTypeToDeepCopy)) { lock (IsStructTypeToDeepCopyDictionaryLocker) { if ( !IsStructTypeToDeepCopyDictionary.TryGetValue( type, out isStructTypeToDeepCopy ) ) { isStructTypeToDeepCopy = IsStructWhichNeedsDeepCopy_NoDictionaryUsed(type); Dictionary 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 ??= []; alreadyCheckedTypes.Add(type); FieldInfo[] allFields = GetAllFields(type); List allFieldTypes = allFields.Select(f => f.FieldType).Distinct().ToList(); bool hasFieldsWithClasses = allFieldTypes.Any(IsClassOtherThanString); if (hasFieldsWithClasses) { return true; } List notBasicStructsTypes = allFieldTypes .Where(IsStructOtherThanBasicValueTypes) .ToList(); List typesToCheck = notBasicStructsTypes .Where(t => !alreadyCheckedTypes.Contains(t)) .ToList(); foreach (Type typeToCheck in typesToCheck) { if (HasInItsHierarchyFieldsWithClasses(typeToCheck, alreadyCheckedTypes)) { return true; } } return false; } 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(); } } } 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(); } } public static class ArrayExtensions { public static void ForEach(this Array array, Action action) { if (array.LongLength == 0) { return; } ArrayTraverse walker = new(array); do { action(array, walker.Position); } while (walker.Step()); } } internal class ArrayTraverse { public int[] Position; private readonly 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; } }