using System.Collections;
using System.Collections.Concurrent;
using System.Reflection;
using Umbraco.Cms.Core.Composing;
namespace Umbraco.Cms.Core.Models;
public static class DeepCloneHelper
{
///
/// Used to avoid constant reflection (perf)
///
private static readonly ConcurrentDictionary PropCache = new();
///
/// Used to deep clone any reference properties on the object (should be done after a MemberwiseClone for which the
/// outcome is 'output')
///
///
///
///
public static void DeepCloneRefProperties(IDeepCloneable input, IDeepCloneable output)
{
Type inputType = input.GetType();
Type outputType = output.GetType();
if (inputType != outputType)
{
throw new InvalidOperationException("Both the input and output types must be the same");
}
// get the property metadata from cache so we only have to figure this out once per type
ClonePropertyInfo[] refProperties = PropCache.GetOrAdd(inputType, type =>
inputType.GetProperties()
.Select(propertyInfo =>
{
if (
// is not attributed with the ignore clone attribute
propertyInfo.GetCustomAttribute() != null
// reference type but not string
|| propertyInfo.PropertyType.IsValueType || propertyInfo.PropertyType == typeof(string)
// settable
|| propertyInfo.CanWrite == false
// non-indexed
|| propertyInfo.GetIndexParameters().Any())
{
return null;
}
if (TypeHelper.IsTypeAssignableFrom(propertyInfo.PropertyType))
{
return new ClonePropertyInfo(propertyInfo) { IsDeepCloneable = true };
}
if (TypeHelper.IsTypeAssignableFrom(propertyInfo.PropertyType)
&& TypeHelper.IsTypeAssignableFrom(propertyInfo.PropertyType) == false)
{
if (propertyInfo.PropertyType.IsGenericType
&& (propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IEnumerable<>)
|| propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>)
|| propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IList<>)
|| propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IReadOnlyCollection<>)))
{
// if it is a IEnumerable<>, IReadOnlyCollection, IList or ICollection<> we'll use a List<> since it implements them all
Type genericType =
typeof(List<>).MakeGenericType(propertyInfo.PropertyType.GetGenericArguments());
return new ClonePropertyInfo(propertyInfo) { GenericListType = genericType };
}
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 derivative of IEnumerable, we can use a list too
return new ClonePropertyInfo(propertyInfo) { GenericListType = typeof(List