2020-07-31 19:07:22 +02:00
using System ;
2021-08-05 03:59:14 +02:00
using System.Collections.Generic ;
2020-10-25 20:11:15 +01:00
using System.Linq ;
2020-07-31 19:07:22 +02:00
using System.Reflection ;
namespace MLEM.Data {
/// <summary>
/// A set of extensions for dealing with copying objects.
/// </summary>
public static class CopyExtensions {
2020-08-01 00:22:29 +02:00
private const BindingFlags DefaultFlags = BindingFlags . Instance | BindingFlags . Public | BindingFlags . NonPublic ;
2021-08-05 03:59:14 +02:00
private static readonly Dictionary < Type , ConstructorInfo > ConstructorCache = new Dictionary < Type , ConstructorInfo > ( ) ;
2020-08-01 00:22:29 +02:00
2020-07-31 19:07:22 +02:00
/// <summary>
/// Creates a shallow copy of the object and returns it.
2020-11-01 15:36:56 +01:00
/// Object creation occurs using a constructor with the <see cref="CopyConstructorAttribute"/> or, if none is present, the first constructor with the correct <see cref="BindingFlags"/>.
2020-07-31 19:07:22 +02:00
/// </summary>
/// <param name="obj">The object to create a shallow copy of</param>
/// <param name="flags">The binding flags for field searching</param>
2020-08-01 00:22:29 +02:00
/// <param name="fieldInclusion">A predicate that determines whether or not the given field should be copied. If null, all fields will be copied.</param>
2020-07-31 19:07:22 +02:00
/// <typeparam name="T">The type of the object to copy</typeparam>
/// <returns>A shallow copy of the object</returns>
2020-08-01 00:22:29 +02:00
public static T Copy < T > ( this T obj , BindingFlags flags = DefaultFlags , Predicate < FieldInfo > fieldInclusion = null ) {
2020-07-31 20:24:59 +02:00
var copy = ( T ) Construct ( typeof ( T ) , flags ) ;
2020-08-01 00:22:29 +02:00
obj . CopyInto ( copy , flags , fieldInclusion ) ;
2020-07-31 19:07:22 +02:00
return copy ;
}
/// <summary>
2020-07-31 20:24:59 +02:00
/// Creates a deep copy of the object and returns it.
2020-11-01 15:36:56 +01:00
/// Object creation occurs using a constructor with the <see cref="CopyConstructorAttribute"/> or, if none is present, the first constructor with the correct <see cref="BindingFlags"/>.
2020-07-31 20:24:59 +02:00
/// </summary>
/// <param name="obj">The object to create a deep copy of</param>
/// <param name="flags">The binding flags for field searching</param>
2020-08-01 00:22:29 +02:00
/// <param name="fieldInclusion">A predicate that determines whether or not the given field should be copied. If null, all fields will be copied.</param>
2020-07-31 20:24:59 +02:00
/// <typeparam name="T">The type of the object to copy</typeparam>
/// <returns>A deep copy of the object</returns>
2020-08-01 00:22:29 +02:00
public static T DeepCopy < T > ( this T obj , BindingFlags flags = DefaultFlags , Predicate < FieldInfo > fieldInclusion = null ) {
2020-07-31 20:24:59 +02:00
var copy = ( T ) Construct ( typeof ( T ) , flags ) ;
2020-08-01 00:22:29 +02:00
obj . DeepCopyInto ( copy , flags , fieldInclusion ) ;
2020-07-31 20:24:59 +02:00
return copy ;
}
/// <summary>
2020-10-06 20:14:57 +02:00
/// Copies the given object <paramref name="obj"/> into the given object <paramref name="otherObj"/> in a shallow manner.
2020-07-31 19:07:22 +02:00
/// </summary>
/// <param name="obj">The object to create a shallow copy of</param>
/// <param name="otherObj">The object to copy into</param>
/// <param name="flags">The binding flags for field searching</param>
2020-08-01 00:22:29 +02:00
/// <param name="fieldInclusion">A predicate that determines whether or not the given field should be copied. If null, all fields will be copied.</param>
2020-07-31 19:07:22 +02:00
/// <typeparam name="T">The type of the object to copy</typeparam>
2020-08-01 00:22:29 +02:00
public static void CopyInto < T > ( this T obj , T otherObj , BindingFlags flags = DefaultFlags , Predicate < FieldInfo > fieldInclusion = null ) {
foreach ( var field in typeof ( T ) . GetFields ( flags ) ) {
if ( fieldInclusion = = null | | fieldInclusion ( field ) )
field . SetValue ( otherObj , field . GetValue ( obj ) ) ;
}
2020-07-31 19:07:22 +02:00
}
2020-07-31 20:24:59 +02:00
/// <summary>
2020-10-06 20:14:57 +02:00
/// Copies the given object <paramref name="obj"/> into the given object <paramref name="otherObj"/> in a deep manner.
2020-11-01 15:36:56 +01:00
/// Object creation occurs using a constructor with the <see cref="CopyConstructorAttribute"/> or, if none is present, the first constructor with the correct <see cref="BindingFlags"/>.
2020-07-31 20:24:59 +02:00
/// </summary>
/// <param name="obj">The object to create a deep copy of</param>
/// <param name="otherObj">The object to copy into</param>
/// <param name="flags">The binding flags for field searching</param>
2020-08-01 00:22:29 +02:00
/// <param name="fieldInclusion">A predicate that determines whether or not the given field should be copied. If null, all fields will be copied.</param>
2020-07-31 20:24:59 +02:00
/// <typeparam name="T">The type of the object to copy</typeparam>
2020-08-01 00:22:29 +02:00
public static void DeepCopyInto < T > ( this T obj , T otherObj , BindingFlags flags = DefaultFlags , Predicate < FieldInfo > fieldInclusion = null ) {
2020-07-31 20:24:59 +02:00
foreach ( var field in obj . GetType ( ) . GetFields ( flags ) ) {
2020-08-01 00:22:29 +02:00
if ( fieldInclusion ! = null & & ! fieldInclusion ( field ) )
continue ;
2020-07-31 20:24:59 +02:00
var val = field . GetValue ( obj ) ;
2020-07-31 20:26:42 +02:00
if ( val = = null | | field . FieldType . IsValueType ) {
// if we're a value type (struct or primitive) or null, we can just set the value
2020-07-31 20:24:59 +02:00
field . SetValue ( otherObj , val ) ;
2020-07-31 20:26:42 +02:00
} else {
2020-07-31 20:24:59 +02:00
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 ) {
2021-08-05 03:59:14 +02:00
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 < CopyConstructorAttribute > ( ) ! = 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 ) ;
}
2020-10-25 20:11:15 +01:00
return constructor . Invoke ( new object [ constructor . GetParameters ( ) . Length ] ) ;
2020-07-31 20:24:59 +02:00
}
2020-07-31 19:07:22 +02:00
}
2020-10-25 20:11:15 +01:00
/// <summary>
/// An attribute that, when added to a constructor, will make that constructor the one used by <see cref="CopyExtensions.Copy{T}"/>, <see cref="CopyExtensions.DeepCopy{T}"/> and <see cref="CopyExtensions.DeepCopyInto{T}"/>.
/// </summary>
[AttributeUsage(AttributeTargets.Constructor)]
2021-12-28 14:56:11 +01:00
public class CopyConstructorAttribute : Attribute { }
2020-07-31 19:07:22 +02:00
}