@@ -14,83 +14,146 @@ public static class ObjectCloneHelper
1414 #region Private Properties
1515
1616 private const BindingFlags Binding = BindingFlags . Instance |
17- BindingFlags . NonPublic | BindingFlags . Public |
17+ BindingFlags . NonPublic |
18+ BindingFlags . Public |
1819 BindingFlags . FlattenHierarchy ;
1920
2021 #endregion
2122
22- public static T Clone < T > ( this object istance , ICollection < string > propertyExcludeList = null )
23+ /// <summary>
24+ /// Clones an object and returns a deep copy of type T.
25+ /// Excluded properties can be specified via a list of property names.
26+ /// </summary>
27+ /// <typeparam name="T">The type of the cloned object.</typeparam>
28+ /// <param name="instance">The object to clone.</param>
29+ /// <param name="propertyExcludeList">A list of property names to exclude from cloning.</param>
30+ /// <returns>A deep copy of the object, or default(T) if the object is null.</returns>
31+ public static T Clone < T > ( this object instance , ICollection < string > propertyExcludeList = null )
2332 {
24- if ( istance == null )
33+ if ( instance == null )
2534 return default ;
2635
27- return ( T ) DeepClone ( istance , propertyExcludeList ) ;
36+ return ( T ) DeepClone ( instance , propertyExcludeList ) ;
2837 }
2938
30- public static object Clone ( this object istance )
39+ /// <summary>
40+ /// Clones an object and returns a deep copy.
41+ /// </summary>
42+ /// <param name="instance">The object to clone.</param>
43+ /// <returns>A deep copy of the object.</returns>
44+ public static object Clone ( this object instance )
3145 {
32- return DeepClone ( istance ) ;
46+ return DeepClone ( instance ) ;
3347 }
3448
35- #region Privat Method Deep Clone
49+ #region Private Method: DeepClone
3650
37- // Clone the object Properties and its children recursively
38- private static object DeepClone ( object istance , ICollection < string > propertyExcludeList = null )
51+ /// <summary>
52+ /// Recursively clones an object and its children.
53+ /// </summary>
54+ /// <param name="instance">The object to clone.</param>
55+ /// <param name="propertyExcludeList">A list of property names to exclude from cloning.</param>
56+ /// <returns>A deep copy of the object.</returns>
57+ private static object DeepClone ( object instance , ICollection < string > propertyExcludeList = null )
3958 {
40- var desireObjectToBeCloned = istance ;
59+ if ( instance == null )
60+ return null ;
4161
42- var primaryType = istance . GetType ( ) ;
62+ var primaryType = instance . GetType ( ) ;
4363
64+ // Handle arrays
4465 if ( primaryType . IsArray )
45- return ( ( Array ) desireObjectToBeCloned ) . Clone ( ) ;
66+ return ( ( Array ) instance ) . Clone ( ) ;
4667
47- object tObject = desireObjectToBeCloned as IList ;
48- if ( tObject != null )
68+ // Handle collections ( IList)
69+ if ( typeof ( IList ) . IsAssignableFrom ( primaryType ) )
4970 {
50- var properties = primaryType . GetProperties ( ) ;
51- // Get the IList Type of the object
52- var customList = typeof ( List < > ) . MakeGenericType
53- ( ( properties [ properties . Length - 1 ] ) . PropertyType ) ;
54- tObject = ( IList ) Activator . CreateInstance ( customList ) ;
55- var list = ( IList ) tObject ;
56- // loop throw each object in the list and clone it
57- foreach ( var item in ( ( IList ) desireObjectToBeCloned ) )
71+ var listType = typeof ( List < > ) . MakeGenericType ( primaryType . GetGenericArguments ( ) . FirstOrDefault ( ) ?? typeof ( object ) ) ;
72+ var listClone = ( IList ) Activator . CreateInstance ( listType ) ;
73+
74+ foreach ( var item in ( IList ) instance )
5875 {
59- if ( item == null )
60- continue ;
61- var value = DeepClone ( item , propertyExcludeList ) ;
62- list ? . Add ( value ) ;
76+ listClone . Add ( item == null ? null : DeepClone ( item , propertyExcludeList ) ) ;
6377 }
78+
79+ return listClone ;
6480 }
65- else
81+
82+ // Handle strings
83+ if ( primaryType == typeof ( string ) )
84+ return string . Copy ( ( string ) instance ) ;
85+
86+ // Handle value types (primitives, structs, enums)
87+ if ( primaryType . IsValueType || primaryType . IsPrimitive || primaryType . IsEnum )
88+ return instance ;
89+
90+ // Handle complex objects
91+ var clonedObject = FormatterServices . GetUninitializedObject ( primaryType ) ;
92+ var fields = primaryType . GetFields ( Binding ) ;
93+
94+ foreach ( var field in fields )
6695 {
67- // if the item is a string then Clone it and return it directly.
68- if ( primaryType == typeof ( string ) )
69- return ( desireObjectToBeCloned as string ) ? . Clone ( ) ;
70-
71- // Create an empty object and ignore its construtore.
72- tObject = FormatterServices . GetUninitializedObject ( primaryType ) ;
73- var fields = desireObjectToBeCloned . GetType ( ) . GetFields ( Binding ) ;
74- foreach ( var property in fields )
96+ // Skip excluded fields
97+ if ( propertyExcludeList != null && propertyExcludeList . Any ( ) )
7598 {
76- if ( ( propertyExcludeList != null ) && ( propertyExcludeList . Any ( ) ) )
77- if ( propertyExcludeList . Contains ( property . Name . ExtractBetween ( "<" , ">" ) ? . FirstOrDefault ( ) ) )
78- continue ;
79-
80- if ( property . IsInitOnly ) // Validate if the property is a writable one.
99+ var fieldName = field . Name . ExtractBetween ( "<" , ">" ) ? . FirstOrDefault ( ) ?? field . Name ;
100+ if ( propertyExcludeList . Contains ( fieldName ) )
81101 continue ;
82- var value = property . GetValue ( desireObjectToBeCloned ) ;
83- if ( property . FieldType . IsClass && property . FieldType != typeof ( string ) )
84- tObject . GetType ( ) . GetField ( property . Name , Binding ) ? . SetValue
85- ( tObject , DeepClone ( value , propertyExcludeList ) ) ;
86- else
87- tObject . GetType ( ) . GetField ( property . Name , Binding ) ? . SetValue ( tObject , value ) ;
88102 }
103+
104+ // Skip readonly fields
105+ if ( field . IsInitOnly )
106+ continue ;
107+
108+ var value = field . GetValue ( instance ) ;
109+
110+ // Clone child objects if they are classes (except strings)
111+ var clonedValue = field . FieldType . IsClass && field . FieldType != typeof ( string )
112+ ? DeepClone ( value , propertyExcludeList )
113+ : value ;
114+
115+ field . SetValue ( clonedObject , clonedValue ) ;
89116 }
90117
91- return tObject ;
118+ return clonedObject ;
92119 }
93120
94121 #endregion
95122 }
96- }
123+
124+ /// <summary>
125+ /// Helper extensions for string operations.
126+ /// </summary>
127+ public static class StringExtensions
128+ {
129+ /// <summary>
130+ /// Extracts a substring between two delimiters.
131+ /// </summary>
132+ /// <param name="input">The input string.</param>
133+ /// <param name="startDelimiter">The starting delimiter.</param>
134+ /// <param name="endDelimiter">The ending delimiter.</param>
135+ /// <returns>An enumerable of substrings found between the delimiters.</returns>
136+ public static IEnumerable < string > ExtractBetween ( this string input , string startDelimiter , string endDelimiter )
137+ {
138+ if ( string . IsNullOrEmpty ( input ) || string . IsNullOrEmpty ( startDelimiter ) || string . IsNullOrEmpty ( endDelimiter ) )
139+ return Enumerable . Empty < string > ( ) ;
140+
141+ var results = new List < string > ( ) ;
142+ var startIndex = 0 ;
143+
144+ while ( ( startIndex = input . IndexOf ( startDelimiter , startIndex ) ) != - 1 )
145+ {
146+ startIndex += startDelimiter . Length ;
147+ var endIndex = input . IndexOf ( endDelimiter , startIndex ) ;
148+
149+ if ( endIndex == - 1 )
150+ break ;
151+
152+ results . Add ( input [ startIndex ..endIndex ] ) ;
153+ startIndex = endIndex + endDelimiter . Length ;
154+ }
155+
156+ return results ;
157+ }
158+ }
159+ }
0 commit comments