@@ -14,83 +14,146 @@ public static class ObjectCloneHelper
14
14
#region Private Properties
15
15
16
16
private const BindingFlags Binding = BindingFlags . Instance |
17
- BindingFlags . NonPublic | BindingFlags . Public |
17
+ BindingFlags . NonPublic |
18
+ BindingFlags . Public |
18
19
BindingFlags . FlattenHierarchy ;
19
20
20
21
#endregion
21
22
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 )
23
32
{
24
- if ( istance == null )
33
+ if ( instance == null )
25
34
return default ;
26
35
27
- return ( T ) DeepClone ( istance , propertyExcludeList ) ;
36
+ return ( T ) DeepClone ( instance , propertyExcludeList ) ;
28
37
}
29
38
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 )
31
45
{
32
- return DeepClone ( istance ) ;
46
+ return DeepClone ( instance ) ;
33
47
}
34
48
35
- #region Privat Method Deep Clone
49
+ #region Private Method: DeepClone
36
50
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 )
39
58
{
40
- var desireObjectToBeCloned = istance ;
59
+ if ( instance == null )
60
+ return null ;
41
61
42
- var primaryType = istance . GetType ( ) ;
62
+ var primaryType = instance . GetType ( ) ;
43
63
64
+ // Handle arrays
44
65
if ( primaryType . IsArray )
45
- return ( ( Array ) desireObjectToBeCloned ) . Clone ( ) ;
66
+ return ( ( Array ) instance ) . Clone ( ) ;
46
67
47
- object tObject = desireObjectToBeCloned as IList ;
48
- if ( tObject != null )
68
+ // Handle collections ( IList)
69
+ if ( typeof ( IList ) . IsAssignableFrom ( primaryType ) )
49
70
{
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 )
58
75
{
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 ) ) ;
63
77
}
78
+
79
+ return listClone ;
64
80
}
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 )
66
95
{
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 ( ) )
75
98
{
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 ) )
81
101
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 ) ;
88
102
}
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 ) ;
89
116
}
90
117
91
- return tObject ;
118
+ return clonedObject ;
92
119
}
93
120
94
121
#endregion
95
122
}
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