<commit_before>using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Umbraco.Core.Models
{
    /// <summary>
    /// Used to attribute properties that have a setter and are a reference type
    /// that should be ignored for cloning when using the DeepCloneHelper
    /// </summary>
    /// <remarks>
    /// 
    /// This attribute must be used:
    /// * when the property is backed by a field but the result of the property is the un-natural data stored in the field
    ///  
    /// This attribute should not be used:
    /// * when the property is virtual
    /// * when the setter performs additional required logic other than just setting the underlying field
    /// 
    /// </remarks>
    internal class DoNotCloneAttribute : Attribute
    {
        
    }

    public static class DeepCloneHelper
    {
        /// <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");
            }

            var refProperties = inputType.GetProperties()
                .Where(x =>
                    //is not attributed with the ignore clone attribute
                    Attribute.GetCustomAttribute(x, typeof(DoNotCloneAttribute)) == null
                    //reference type but not string
                    && x.PropertyType.IsValueType == false && x.PropertyType != typeof (string)
                        //settable
                    && x.CanWrite
                        //non-indexed
                    && x.GetIndexParameters().Any() == false);

            foreach (var propertyInfo in refProperties)
            {
                if (TypeHelper.IsTypeAssignableFrom<IDeepCloneable>(propertyInfo.PropertyType))
                {
                    //this ref property is also deep cloneable so clone it<commit_msg>Adds a simple cache mechanism to hold reflected property info when cloning - reduces perf to 10% of what it used to be<commit_after>using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Umbraco.Core.Models
{
    /// <summary>
    /// Used to attribute properties that have a setter and are a reference type
    /// that should be ignored for cloning when using the DeepCloneHelper
    /// </summary>
    /// <remarks>
    /// 
    /// This attribute must be used:
    /// * when the property is backed by a field but the result of the property is the un-natural data stored in the field
    ///  
    /// This attribute should not be used:
    /// * when the property is virtual
    /// * when the setter performs additional required logic other than just setting the underlying field
    /// 
    /// </remarks>
    internal class DoNotCloneAttribute : Attribute
    {
        
    }

    public static class DeepCloneHelper
    {
        /// <summary>
        /// Used to avoid constant reflection (perf)
        /// </summary>
        private static readonly ConcurrentDictionary<Type, PropertyInfo[]> PropCache = new ConcurrentDictionary<Type, PropertyInfo[]>(); 

        /// <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");
            }

            var refProperties = PropCache.GetOrAdd(inputType, type =>
                inputType.GetProperties()
                    .Where(x =>
                        //is not attributed with the ignore clone attribute
                    Attribute.GetCustomAttribute(x, typeof(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());

            foreach (var propertyInfo in refProperties)
            {
                if (TypeHelper.IsTypeAssignableFrom<IDeepCloneable>(propertyInfo.PropertyType))
                {
                    //this ref property is also deep cloneable so clone it
{'fraction_non_alphanumeric': 0.09014084507042254, 'fraction_numerical': 0.0003755868544600939, 'mean_word_length': 1.8164992067689054, 'pattern_counts': {'":': 0, '<': 33, '<?xml version=': 0, '>': 36, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6708557', 'n_tokens_mistral': 1243, 'n_tokens_neox': 1220, 'n_words': 448}