using System.Collections; using System.Linq.Expressions; using System.Reflection; using System.Runtime.Serialization; using Pcg; namespace PkmnLib.Static.Utils; /// /// Marks the type as deep cloneable. This means that when a deep clone is made, the object will be cloned recursively. /// Any reference types that are not marked as deep cloneable will be copied by reference, and not cloned. /// public interface IDeepCloneable; /// /// Handles deep cloning of objects. /// public static class DeepCloneHandler { /// /// This will clone an object including all fields and properties, and recursively clone any reference types /// that are marked as deep cloneable. Value types will be copied by value, and reference types that are not marked as /// deep cloneable will be copied by reference. /// /// Recursive references will be handled correctly, and will only be cloned once, to prevent infinite loops and invalid /// references. /// public static T DeepClone(this T? obj, Dictionary<(Type, int), object>? objects = null) where T : IDeepCloneable => (T)DeepClone((object?)obj, objects)!; private static object? DeepClone(this object? obj, Dictionary<(Type, int), object>? objects = null) { if (obj == null) return null; if (objects != null && objects.TryGetValue((obj.GetType(), obj.GetHashCode()), out var value)) return value; var type = obj.GetType(); // We use GetUninitializedObject to create an object without calling the constructor. This is necessary to prevent // side effects from the constructor, and to not require a parameterless constructor. var newObj = FormatterServices.GetUninitializedObject(type)!; // If the objects dictionary is null, we create a new one. We use this dictionary to keep track of objects that have // already been cloned, so we can re-use them instead of cloning them again. This is necessary to prevent infinite // loops and invalid references. objects ??= new Dictionary<(Type, int), object>(); objects.Add((obj.GetType(), obj.GetHashCode()), newObj); var expressions = GetDeepCloneExpressions(type); foreach (var (getter, setter) in expressions) { var v = getter.Invoke(obj); if (v == null) continue; var cloned = DeepCloneInternal(v, v.GetType(), objects); setter.Invoke(newObj, cloned); } return newObj; } private static readonly HashSet ExternalDeepCloneTypes = new() { typeof(PcgRandom), typeof(Pcg32Single), }; private static object? DeepCloneInternal(object? obj, Type type, Dictionary<(Type, int), object> objects) { if (obj == null) return null; // If the object is a value type or a string, we can just return it. if (type.IsValueType || type == typeof(string)) return obj; // If the object is marked as deep cloneable, we will clone it. if (type.GetInterface(nameof(IDeepCloneable)) != null || ExternalDeepCloneTypes.Contains(type)) { // If the object is already cloned, we return the cloned object to prevent infinite loops and invalid references. if (objects.TryGetValue((obj.GetType(), obj.GetHashCode()), out var value)) return value; var o = DeepClone(obj, objects); return o; } if (type.IsArray) { // ReSharper disable once SuspiciousTypeConversion.Global var array = (Array)obj; var newArray = Array.CreateInstance(type.GetElementType()!, array.Length); for (var i = 0; i < array.Length; i++) { newArray.SetValue(DeepCloneInternal(array.GetValue(i), type.GetElementType()!, objects), i); } return newArray; } if (type.IsGenericType) { var genericType = type.GetGenericTypeDefinition(); if (genericType == typeof(List<>)) { // ReSharper disable once SuspiciousTypeConversion.Global var list = (IList)obj; var newList = (IList)Activator.CreateInstance(type); foreach (var item in list) newList.Add(DeepCloneInternal(item, type.GetGenericArguments()[0], objects)); return newList; } if (genericType == typeof(Dictionary<,>)) { // ReSharper disable once SuspiciousTypeConversion.Global var dictionary = (IDictionary)obj; var newDictionary = (IDictionary)Activator.CreateInstance(type); foreach (DictionaryEntry entry in dictionary) { newDictionary.Add(DeepCloneInternal(entry.Key, type.GetGenericArguments()[0], objects)!, DeepCloneInternal(entry.Value, type.GetGenericArguments()[1], objects)); } return newDictionary; } } return obj; } /// /// Helper method to get the compiled expressions for deep cloning a type. This will create a getter and setter for each field /// in the type, which can be used to clone the object. /// /// /// This method is thread safe, and will only create the expressions once for each type. It returns compiled expressions for /// each field in the type, so that we can get high performance deep cloning. /// private static (Func getter, Action setter)[] GetDeepCloneExpressions(Type type) { // We use a lock here to prevent multiple threads from trying to create the expressions at the same time. lock (DeepCloneExpressions) { if (DeepCloneExpressions.TryGetValue(type, out var value)) return value; var fields = GetFields(type).ToArray(); var expressions = new (Func getter, Action setter)[fields.Length]; for (var i = 0; i < fields.Length; i++) { var field = fields[i]; // Create a compiled getter for the field. // 1. Set up the instance parameter. var obj = Expression.Parameter(typeof(object)); // 2. Cast the instance (which we want to pass as an object) to the correct type. var cast = Expression.Convert(obj, type); // 3. Get the field value from the instance. var get = Expression.Field(cast, field); // 4. Cast the field value to an object. var getCasted = Expression.Convert(get, typeof(object)); // 5. Wrap the cast in a lambda so we can compile it. var lambda = Expression.Lambda>(getCasted, obj); // This is a slight hack to allow us to set readonly fields. We can't set them directly through expression trees, // as Expression.Assign checks for this. We can however set them through reflection, so we create a setter that // does this. This is not ideal as it is slower, but works for now. if (field.IsInitOnly) { void Setter(object instance, object? v) { field.SetValue(instance, v); } expressions[i] = (getter: lambda.Compile(), Setter); } else { // Create a compiled setter for the field. // 1. Set up the parameter for the value. var valueLambda = Expression.Parameter(typeof(object)); // 2. Cast the value to the correct type. var valueCast = Expression.Convert(valueLambda, field.FieldType); // 3. Assign the value to the field. var assign = Expression.Assign(Expression.Field(cast, field), valueCast); // 4. Wrap the assign in a lambda so we can compile it. var set = Expression.Lambda>(assign, obj, valueLambda); expressions[i] = (getter: lambda.Compile(), setter: set.Compile()); } } DeepCloneExpressions.Add(type, expressions); return expressions; } } private static IEnumerable GetFields(Type type) { IEnumerable fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly); // Note that we do the above with DeclaredOnly, while we do want to get the fields from the base type. // This is because even without DeclaredOnly, we will only get public fields from our base type. As we want // to get all fields, we need to do this recursively. if (type.BaseType != null) fields = fields.Concat(GetFields(type.BaseType)); return fields; } private static readonly Dictionary getter, Action setter)[]> DeepCloneExpressions = new(); }