2014-04-16 13:39:42 +10:00
|
|
|
using System;
|
|
|
|
|
using System.Collections;
|
2014-08-20 19:27:53 -06:00
|
|
|
using System.Collections.Concurrent;
|
2014-04-16 13:39:42 +10:00
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
2014-05-16 11:34:10 +10:00
|
|
|
using System.Reflection;
|
2014-04-16 13:39:42 +10:00
|
|
|
|
|
|
|
|
namespace Umbraco.Core.Models
|
|
|
|
|
{
|
2014-04-16 13:40:20 +10:00
|
|
|
public static class DeepCloneHelper
|
2014-04-16 13:39:42 +10:00
|
|
|
{
|
2014-08-20 19:27:53 -06:00
|
|
|
/// <summary>
|
|
|
|
|
/// Used to avoid constant reflection (perf)
|
|
|
|
|
/// </summary>
|
|
|
|
|
private static readonly ConcurrentDictionary<Type, PropertyInfo[]> PropCache = new ConcurrentDictionary<Type, PropertyInfo[]>();
|
|
|
|
|
|
2014-04-16 13:39:42 +10:00
|
|
|
/// <summary>
|
|
|
|
|
/// Used to deep clone any reference properties on the object (should be done after a MemberwiseClone for which the outcome is 'output')
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="input"></param>
|
|
|
|
|
/// <param name="output"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public static void DeepCloneRefProperties(IDeepCloneable input, IDeepCloneable output)
|
|
|
|
|
{
|
|
|
|
|
var inputType = input.GetType();
|
|
|
|
|
var outputType = output.GetType();
|
|
|
|
|
|
|
|
|
|
if (inputType != outputType)
|
|
|
|
|
{
|
|
|
|
|
throw new InvalidOperationException("Both the input and output types must be the same");
|
|
|
|
|
}
|
|
|
|
|
|
2014-08-20 19:27:53 -06:00
|
|
|
var refProperties = PropCache.GetOrAdd(inputType, type =>
|
|
|
|
|
inputType.GetProperties()
|
|
|
|
|
.Where(x =>
|
|
|
|
|
//is not attributed with the ignore clone attribute
|
|
|
|
|
x.GetCustomAttribute<DoNotCloneAttribute>() == null
|
|
|
|
|
//reference type but not string
|
|
|
|
|
&& x.PropertyType.IsValueType == false && x.PropertyType != typeof (string)
|
|
|
|
|
//settable
|
|
|
|
|
&& x.CanWrite
|
|
|
|
|
//non-indexed
|
|
|
|
|
&& x.GetIndexParameters().Any() == false)
|
|
|
|
|
.ToArray());
|
2014-04-16 13:39:42 +10:00
|
|
|
|
|
|
|
|
foreach (var propertyInfo in refProperties)
|
|
|
|
|
{
|
|
|
|
|
if (TypeHelper.IsTypeAssignableFrom<IDeepCloneable>(propertyInfo.PropertyType))
|
|
|
|
|
{
|
|
|
|
|
//this ref property is also deep cloneable so clone it
|
|
|
|
|
var result = (IDeepCloneable)propertyInfo.GetValue(input, null);
|
|
|
|
|
|
|
|
|
|
if (result != null)
|
|
|
|
|
{
|
|
|
|
|
//set the cloned value to the property
|
|
|
|
|
propertyInfo.SetValue(output, result.DeepClone(), null);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (TypeHelper.IsTypeAssignableFrom<IEnumerable>(propertyInfo.PropertyType)
|
|
|
|
|
&& TypeHelper.IsTypeAssignableFrom<string>(propertyInfo.PropertyType) == false)
|
|
|
|
|
{
|
|
|
|
|
IList newList;
|
|
|
|
|
if (propertyInfo.PropertyType.IsGenericType
|
2014-04-16 15:33:21 +10:00
|
|
|
&& (propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IEnumerable<>)
|
|
|
|
|
|| propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>)
|
2014-04-16 13:39:42 +10:00
|
|
|
|| propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IList<>)))
|
|
|
|
|
{
|
|
|
|
|
//if it is a IEnumerable<>, IList<T> or ICollection<> we'll use a List<>
|
2014-04-16 15:33:21 +10:00
|
|
|
var genericType = typeof(List<>).MakeGenericType(propertyInfo.PropertyType.GetGenericArguments());
|
|
|
|
|
newList = (IList)Activator.CreateInstance(genericType);
|
2014-04-16 13:39:42 +10:00
|
|
|
}
|
|
|
|
|
else if (propertyInfo.PropertyType.IsArray
|
|
|
|
|
|| (propertyInfo.PropertyType.IsInterface && propertyInfo.PropertyType.IsGenericType == false))
|
|
|
|
|
{
|
|
|
|
|
//if its an array, we'll create a list to work with first and then convert to array later
|
|
|
|
|
//otherwise if its just a regular derivitave of IEnumerable, we can use a list too
|
|
|
|
|
newList = new List<object>();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2015-12-15 14:47:19 +01:00
|
|
|
//skip instead of trying to create instance of abstract or interface
|
2015-12-10 14:33:52 +13:00
|
|
|
if (propertyInfo.PropertyType.IsAbstract || propertyInfo.PropertyType.IsInterface)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2015-12-15 14:47:19 +01:00
|
|
|
|
|
|
|
|
//its a custom IEnumerable, we'll try to create it
|
2014-04-16 13:39:42 +10:00
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var custom = Activator.CreateInstance(propertyInfo.PropertyType);
|
|
|
|
|
//if it's an IList we can work with it, otherwise we cannot
|
|
|
|
|
newList = custom as IList;
|
|
|
|
|
if (newList == null)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception)
|
|
|
|
|
{
|
|
|
|
|
//could not create this type so we'll skip it
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var enumerable = (IEnumerable)propertyInfo.GetValue(input, null);
|
|
|
|
|
if (enumerable == null) continue;
|
|
|
|
|
|
2014-04-16 15:33:21 +10:00
|
|
|
var isUsableType = true;
|
|
|
|
|
|
|
|
|
|
//now clone each item
|
2014-04-16 13:39:42 +10:00
|
|
|
foreach (var o in enumerable)
|
|
|
|
|
{
|
2014-04-16 15:33:21 +10:00
|
|
|
//first check if the item is deep cloneable and copy that way
|
2014-04-16 13:39:42 +10:00
|
|
|
var dc = o as IDeepCloneable;
|
|
|
|
|
if (dc != null)
|
|
|
|
|
{
|
|
|
|
|
newList.Add(dc.DeepClone());
|
|
|
|
|
}
|
2014-04-16 15:33:21 +10:00
|
|
|
else if (o is string || o.GetType().IsValueType)
|
|
|
|
|
{
|
|
|
|
|
//check if the item is a value type or a string, then we can just use it
|
|
|
|
|
newList.Add(o);
|
|
|
|
|
}
|
|
|
|
|
else
|
2014-04-16 13:39:42 +10:00
|
|
|
{
|
2014-04-16 15:33:21 +10:00
|
|
|
//this will occur if the item is not a string or value type or IDeepCloneable, in this case we cannot
|
|
|
|
|
// clone each element, we'll need to skip this property, people will have to manually clone this list
|
|
|
|
|
isUsableType = false;
|
|
|
|
|
break;
|
2014-04-16 13:39:42 +10:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-04-16 15:33:21 +10:00
|
|
|
//if this was not usable, skip this property
|
|
|
|
|
if (isUsableType == false)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2014-04-16 13:39:42 +10:00
|
|
|
if (propertyInfo.PropertyType.IsArray)
|
|
|
|
|
{
|
|
|
|
|
//need to convert to array
|
|
|
|
|
var arr = (object[])Activator.CreateInstance(propertyInfo.PropertyType, newList.Count);
|
|
|
|
|
for (int i = 0; i < newList.Count; i++)
|
|
|
|
|
{
|
|
|
|
|
arr[i] = newList[i];
|
|
|
|
|
}
|
|
|
|
|
//set the cloned collection
|
|
|
|
|
propertyInfo.SetValue(output, arr, null);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
//set the cloned collection
|
|
|
|
|
propertyInfo.SetValue(output, newList, null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|