using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace MLEM.Data {
///
/// A set of extensions for dealing with copying objects.
///
public static class CopyExtensions {
private const BindingFlags DefaultFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
private static readonly Dictionary ConstructorCache = new Dictionary();
///
/// Creates a shallow copy of the object and returns it.
/// Object creation occurs using a constructor with the or, if none is present, the first constructor with the correct .
///
/// The object to create a shallow copy of
/// The binding flags for field searching
/// A predicate that determines whether or not the given field should be copied. If null, all fields will be copied.
/// The type of the object to copy
/// A shallow copy of the object
public static T Copy(this T obj, BindingFlags flags = DefaultFlags, Predicate fieldInclusion = null) {
var copy = (T) Construct(typeof(T), flags);
obj.CopyInto(copy, flags, fieldInclusion);
return copy;
}
///
/// Creates a deep copy of the object and returns it.
/// Object creation occurs using a constructor with the or, if none is present, the first constructor with the correct .
///
/// The object to create a deep copy of
/// The binding flags for field searching
/// A predicate that determines whether or not the given field should be copied. If null, all fields will be copied.
/// The type of the object to copy
/// A deep copy of the object
public static T DeepCopy(this T obj, BindingFlags flags = DefaultFlags, Predicate fieldInclusion = null) {
var copy = (T) Construct(typeof(T), flags);
obj.DeepCopyInto(copy, flags, fieldInclusion);
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
/// The binding flags for field searching
/// A predicate that determines whether or not the given field should be copied. If null, all fields will be copied.
/// The type of the object to copy
public static void CopyInto(this T obj, T otherObj, BindingFlags flags = DefaultFlags, Predicate fieldInclusion = null) {
foreach (var field in typeof(T).GetFields(flags)) {
if (fieldInclusion == null || fieldInclusion(field))
field.SetValue(otherObj, field.GetValue(obj));
}
}
///
/// Copies the given object into the given object in a deep manner.
/// Object creation occurs using a constructor with the or, if none is present, the first constructor with the correct .
///
/// The object to create a deep copy of
/// The object to copy into
/// The binding flags for field searching
/// A predicate that determines whether or not the given field should be copied. If null, all fields will be copied.
/// The type of the object to copy
public static void DeepCopyInto(this T obj, T otherObj, BindingFlags flags = DefaultFlags, Predicate fieldInclusion = null) {
foreach (var field in obj.GetType().GetFields(flags)) {
if (fieldInclusion != null && !fieldInclusion(field))
continue;
var val = field.GetValue(obj);
if (val == null || field.FieldType.IsValueType) {
// if we're a value type (struct or primitive) or null, we can just set the value
field.SetValue(otherObj, val);
} else {
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);
}
}
}
private static object Construct(Type t, BindingFlags flags) {
if (!ConstructorCache.TryGetValue(t, out var constructor)) {
var constructors = t.GetConstructors(flags);
// find a contructor with the correct attribute
constructor = constructors.FirstOrDefault(c => c.GetCustomAttribute() != null);
// find a parameterless construcotr
if (constructor == null)
constructor = t.GetConstructor(flags, null, Type.EmptyTypes, null);
// fall back to the first constructor
if (constructor == null)
constructor = constructors.FirstOrDefault();
if (constructor == null)
throw new NullReferenceException($"Type {t} does not have a constructor with the required visibility");
ConstructorCache.Add(t, constructor);
}
return constructor.Invoke(new object[constructor.GetParameters().Length]);
}
}
///
/// An attribute that, when added to a constructor, will make that constructor the one used by , and .
///
[AttributeUsage(AttributeTargets.Constructor)]
public class CopyConstructorAttribute : Attribute {}
}