diff --git a/MLEM.Data/CopyExtensions.cs b/MLEM.Data/CopyExtensions.cs index dbe03b9..4341509 100644 --- a/MLEM.Data/CopyExtensions.cs +++ b/MLEM.Data/CopyExtensions.cs @@ -16,13 +16,27 @@ namespace MLEM.Data { /// The type of the object to copy /// A shallow copy of the object public static T Copy(this T obj, BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) { - var copy = (T) typeof(T).GetConstructor(Type.EmptyTypes).Invoke(null); + var copy = (T) Construct(typeof(T), flags); obj.CopyInto(copy, flags); return copy; } /// - /// Copies the given object into the given object . + /// Creates a deep copy of the object and returns it. + /// Note that, for this to work correctly, needs to contain a parameterless constructor. + /// + /// The object to create a deep copy of + /// The binding flags for field searching + /// The type of the object to copy + /// A deep copy of the object + public static T DeepCopy(this T obj, BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) { + var copy = (T) Construct(typeof(T), flags); + obj.DeepCopyInto(copy, flags); + return copy; + } + + /// + /// Copies the given object into the given object in a shallow manner. /// /// The object to create a shallow copy of /// The object to copy into @@ -33,5 +47,38 @@ namespace MLEM.Data { field.SetValue(otherObj, field.GetValue(obj)); } + /// + /// Copies the given object into the given object in a deep manner. + /// Note that, for this to work correctly, each type that should be constructed below the topmost level needs to contanin a parameterless constructor. + /// + /// The object to create a deep copy of + /// The object to copy into + /// The binding flags for field searching + /// The type of the object to copy + public static void DeepCopyInto(this T obj, T otherObj, BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) { + foreach (var field in obj.GetType().GetFields(flags)) { + var val = field.GetValue(obj); + if (field.FieldType.IsValueType) { + // if we're a value type (struct or primitive), we can just set the value + field.SetValue(otherObj, val); + } else if (val != null) { + var otherVal = field.GetValue(otherObj); + // if the object we want to copy into doesn't have a value yet, we create one + if (otherVal == null) { + otherVal = Construct(field.FieldType, flags); + field.SetValue(otherObj, otherVal); + } + val.DeepCopyInto(otherVal, flags); + } else { + // if the value is null, we ensure that the value of the resulting object is also reset + field.SetValue(otherObj, null); + } + } + } + + private static object Construct(Type t, BindingFlags flags) { + return t.GetConstructor(flags, null, Type.EmptyTypes, null).Invoke(null); + } + } } \ No newline at end of file diff --git a/Sandbox/GameImpl.cs b/Sandbox/GameImpl.cs index 9012b2f..6c0ee56 100644 --- a/Sandbox/GameImpl.cs +++ b/Sandbox/GameImpl.cs @@ -92,17 +92,19 @@ namespace Sandbox { var obj = new Test { Vec = new Vector2(10, 20), Point = new Point(20, 30), - Rectangle = new Rectangle(1, 2, 3, 4), - RectangleF = new RectangleF(4, 5, 6, 7).ToMlem(), - Dir = Direction2.Left + Dir = Direction2.Left, + OtherTest = new Test { + Vec = new Vector2(70, 30), + Dir = Direction2.Right + } }; Console.WriteLine(obj); - var copy = obj.Copy(); + var copy = obj.DeepCopy(); Console.WriteLine(copy); - var intoCopy = new Test(); - obj.CopyInto(intoCopy); + var intoCopy = new Test {OtherTest = new Test()}; + obj.DeepCopyInto(intoCopy); Console.WriteLine(intoCopy); var writer = new StringWriter(); @@ -214,12 +216,11 @@ namespace Sandbox { public Vector2 Vec; public Point Point; - public Rectangle Rectangle; - public MLEM.Misc.RectangleF RectangleF; public Direction2 Dir { get; set; } + public Test OtherTest; public override string ToString() { - return $"{nameof(this.Vec)}: {this.Vec}, {nameof(this.Point)}: {this.Point}, {nameof(this.Rectangle)}: {this.Rectangle}, {nameof(this.RectangleF)}: {this.RectangleF}, {nameof(this.Dir)}: {this.Dir}"; + return $"{this.GetHashCode()}: {nameof(this.Vec)}: {this.Vec}, {nameof(this.Point)}: {this.Point}, {nameof(this.OtherTest)}: {this.OtherTest}, {nameof(this.Dir)}: {this.Dir}"; } }