18using System.Collections.Generic;
20using System.Reflection;
21using System.Collections;
23using System.Runtime.CompilerServices;
29 public new bool Equals(
object x,
object y) => ReferenceEquals(x, y);
30 public int GetHashCode(
object obj) => RuntimeHelpers.GetHashCode(obj);
37 internal static class ObjectCopier
39 public static T DeepCopyObject<T>(dynamic source, T destination, DeepCopy deepCopy, HashSet<object> visited =
null)
41 if (source ==
null || destination ==
null)
43 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
"Source and/or destination must not be null. Returning whichever is not null or default(T).",
null, GPALObjectType.None);
44 return null != source ? source :
null != destination ? destination :
default(T);
47 visited = visited ??
new HashSet<object>(
new ReferenceEqualityComparer());
48 if (!visited.Add(source))
50 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Circular reference detected in DeepCopyObject @ [{source}].", source, GPALObjectType.None);
54 Type sourceType = source.GetType();
57 if (typeof(IDictionary).IsAssignableFrom(sourceType))
59 if (!typeof(IDictionary).IsAssignableFrom(destination.GetType()))
61 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Destination [{destination.GetType()}] is not a compatible [{sourceType}] dictionary type.", source, GPALObjectType.None);
64 CopyDictionary((IDictionary)source, (IDictionary)destination, deepCopy, visited);
67 else if (typeof(IEnumerable).IsAssignableFrom(sourceType) && !IsImmutableType(sourceType))
69 if (!typeof(IList).IsAssignableFrom(destination.GetType()))
71 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Destination [{destination.GetType()}] is not a compatible list type.", source, GPALObjectType.None);
74 CopyObjects((IEnumerable)source, (IList)destination, deepCopy, visited);
79 destination = CopyMembers(source, destination, deepCopy, visited);
85 static void CopyDictionary(IDictionary sourceDict, IDictionary destinationDict, DeepCopy deepCopy, HashSet<object> visited)
87 destinationDict.Clear();
89 foreach (var key
in sourceDict.Keys)
93 if (key is
string strKey)
95 else if (key is
int intKey)
101 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"Unsupported dynamic key type [{key.GetType().Name}]. Using ToString as fallback.", key, GPALObjectType.None);
102 safeKey = key.ToString();
105 var sourceValue = sourceDict[key];
106 if (sourceValue ==
null)
108 destinationDict[safeKey] =
null;
112 Type valueType = sourceValue.GetType();
114 destinationDict[safeKey] = sourceValue;
118 static IList CopyObjects(IEnumerable sourceObjects, IList destinationObjects, DeepCopy deepCopy, HashSet<object> visited)
120 destinationObjects.Clear();
121 foreach (var source
in sourceObjects)
125 destinationObjects.Add(
null);
129 var destination = ConverterHelper.InstantiateOne(source.GetType());
130 destination = DeepCopyObject(source, destination, deepCopy, visited);
131 destinationObjects.Add(destination);
133 return destinationObjects;
136 public static T CopyMembers<T>(
object source, T destination, DeepCopy deepCopy, HashSet<object> visited)
138 if (source ==
null || destination ==
null)
140 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
"Source or destination is null in CopyMembers.",
null, GPALObjectType.None);
144 Type sourceType = source.GetType();
145 Type destType = destination.GetType();
148 if (source is IGPALGrid<string>)
150 return (T)sourceType.GetMethod(
"Clone")?.Invoke(source,
null) ?? destination;
154 if (deepCopy == DeepCopy.Fields || deepCopy == DeepCopy.FieldsAndProperties)
156 FieldInfo[] fields = sourceType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
157 foreach (FieldInfo field
in fields)
159 if (IsSystemInternalType(field.FieldType) || IsCultureRelatedType(field.FieldType))
166 object sourceValue = field.GetValue(source);
167 if (sourceValue ==
null)
169 field.SetValue(destination,
null);
173 Type fieldType = field.FieldType;
175 if (typeof(IEnumerable).IsAssignableFrom(fieldType) && !IsImmutableType(fieldType))
177 object destEnumerable = ConverterHelper.InstantiateOne(fieldType);
178 if (destEnumerable ==
null)
180 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Failed to instantiate enumerable type [{fieldType.Name}].", source, GPALObjectType.None);
184 if (typeof(IDictionary).IsAssignableFrom(fieldType))
186 CopyDictionary((IDictionary)sourceValue, (IDictionary)destEnumerable, deepCopy, visited);
188 else if (typeof(IList).IsAssignableFrom(fieldType))
190 CopyObjects((IEnumerable)sourceValue, (IList)destEnumerable, deepCopy, visited);
194 CopyGenericEnumerable((IEnumerable)sourceValue, destEnumerable, fieldType, deepCopy, visited);
197 field.SetValue(destination, destEnumerable);
199 else if (!fieldType.IsValueType && !IsImmutableType(fieldType) && !IsSystemInternalType(fieldType) && !IsCultureRelatedType(fieldType))
201 object destValue = ConverterHelper.InstantiateOne(fieldType);
202 destValue = DeepCopyObject(sourceValue, destValue, deepCopy, visited);
203 field.SetValue(destination, destValue);
207 field.SetValue(destination, sourceValue);
212 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Failed to copy field [{field.Name}] of type [{sourceType.Name}].", source, GPALObjectType.Other, ex);
218 if (deepCopy == DeepCopy.Properties || deepCopy == DeepCopy.FieldsAndProperties)
220 PropertyInfo[] properties = sourceType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
221 foreach (PropertyInfo property
in properties)
223 if (property.GetIndexParameters().Length > 0 ||
224 property.GetCustomAttributes(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute),
false).Length > 0 ||
225 (property.DeclaringType !=
null && (typeof(ICollection).IsAssignableFrom(property.DeclaringType) ||
226 IsSystemInternalType(property.DeclaringType) ||
227 IsCultureRelatedType(property.DeclaringType))))
234 if (property.CanRead && property.CanWrite)
236 object sourceValue =
property.GetValue(source);
237 if (sourceValue ==
null)
239 property.SetValue(destination,
null);
243 Type propType =
property.PropertyType;
245 if (typeof(IEnumerable).IsAssignableFrom(propType) && !IsImmutableType(propType))
247 object destEnumerable = ConverterHelper.InstantiateOne(propType);
248 if (destEnumerable ==
null)
250 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Failed to instantiate enumerable type [{propType.Name}].", source, GPALObjectType.None);
254 if (typeof(IDictionary).IsAssignableFrom(propType))
256 CopyDictionary((IDictionary)sourceValue, (IDictionary)destEnumerable, deepCopy, visited);
258 else if (typeof(IList).IsAssignableFrom(propType))
260 CopyObjects((IEnumerable)sourceValue, (IList)destEnumerable, deepCopy, visited);
264 CopyGenericEnumerable((IEnumerable)sourceValue, destEnumerable, propType, deepCopy, visited);
267 property.SetValue(destination, destEnumerable);
269 else if (!propType.IsValueType && !IsImmutableType(propType) && !IsSystemInternalType(propType) && !IsCultureRelatedType(propType))
271 object destValue = ConverterHelper.InstantiateOne(propType);
272 destValue = DeepCopyObject(sourceValue, destValue, deepCopy, visited);
273 property.SetValue(destination, destValue);
277 property.SetValue(destination, sourceValue);
283 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Failed to copy property [{property.Name}] of type [{sourceType.Name}].", source, GPALObjectType.Other, ex);
291 private static void CopyGenericEnumerable(IEnumerable sourceEnumerable,
object destEnumerable, Type enumerableType, DeepCopy deepCopy, HashSet<object> visited)
293 MethodInfo addMethod = enumerableType.GetMethod(
"Add");
294 if (addMethod ==
null)
296 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"No Add method found for type [{enumerableType.Name}].",
null, GPALObjectType.None);
300 foreach (var item
in sourceEnumerable)
304 addMethod.Invoke(destEnumerable,
new object[] {
null });
308 Type itemType = item.GetType();
311 if (!itemType.IsValueType && !IsImmutableType(itemType) && !IsSystemInternalType(itemType) && !IsCultureRelatedType(itemType))
313 if (!visited.Add(item))
315 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Circular reference detected for item of type [{itemType.Name}].", item, GPALObjectType.None);
319 destItem = ConverterHelper.InstantiateOne(itemType);
320 destItem = DeepCopyObject(item, destItem, deepCopy, visited);
327 addMethod.Invoke(destEnumerable,
new object[] { destItem });
331 private static bool IsImmutableType(Type type)
333 return type == typeof(
string) ||
334 type == typeof(DateTime) ||
335 type == typeof(TimeSpan) ||
336 type == typeof(decimal) ||
337 type == typeof(Guid);
340 private static bool IsSystemInternalType(Type type)
342 return type.Namespace !=
null &&
343 (type.Namespace.StartsWith(
"System.Reflection") ||
344 type.Namespace.StartsWith(
"System.Runtime") ||
345 type.Namespace.StartsWith(
"System.Threading") ||
346 type.Namespace.StartsWith(
"System.Security"));
349 private static bool IsCultureRelatedType(Type type)
351 return type == typeof(System.Globalization.CultureInfo) ||
352 type.Namespace ==
"System.Globalization";
355 public static int GetItemCount(dynamic propertyOrField, dynamic source)
359 if (source is IEnumerable<object> objectEnumerable)
361 return objectEnumerable.Count();
363 else if (source is Array array)
367 else if (source is ICollection collection)
369 return collection.Count;
371 else if (
null != propertyOrField)
373 dynamic enumeration = propertyOrField.GetValue(source);
374 if (
null != enumeration)
375 foreach (dynamic item
in enumeration)
383 foreach (var item
in source)
392 static bool IsPropertyEnumerable(PropertyInfo property)
394 return typeof(IEnumerable).IsAssignableFrom(property.PropertyType);
397 static bool IsFieldEnumerable(FieldInfo field)
399 return typeof(IEnumerable).IsAssignableFrom(field.FieldType);