1+ // copied from https://raw.githubusercontent.com/Alenah091/FastDeepCloner/master/FastDeepCloner.cs because I need .NET 4.0 for tests
2+ using System ;
3+ using System . Collections ;
4+ using System . Collections . Generic ;
5+ using System . Linq ;
6+ using System . Reflection ;
7+ using System . Runtime . Serialization ;
8+
9+ namespace Force . DeepCloner . Tests . Imported
10+ {
11+ /// <summary>
12+ /// Supports cloning, which creates a new instance of a class with the same value as an existing instance.
13+ /// Used to deep clone objects, whether they are serializable or not.
14+ /// </summary>
15+ public class FastDeepCloner
16+ {
17+ #region Private fields
18+ private const BindingFlags Binding = BindingFlags . Instance | BindingFlags . NonPublic | BindingFlags . Public | BindingFlags . FlattenHierarchy ;
19+ private Type _primaryType ;
20+ private object _desireObjectToBeCloned ;
21+ private int _length ;
22+ private bool _isArray ;
23+ private bool _isDictionary ;
24+ private bool _isList ;
25+ private int _rank ;
26+ private bool ? _initPublicOnly ;
27+ private Type _ignorePropertiesWithAttribute ;
28+ private static IDictionary < Type , List < FieldInfo > > _cachedFields ;
29+ private static IDictionary < Type , List < PropertyInfo > > _cachedPropertyInfo ;
30+ private FieldType _fieldType ;
31+ private IDictionary < string , bool > _alreadyCloned ;
32+ #endregion
33+
34+ #region Constructors
35+ public FastDeepCloner ( object desireObjectToBeCloned , FieldType fieldType )
36+ {
37+ if ( desireObjectToBeCloned == null )
38+ {
39+ throw new ArgumentNullException ( "desireObjectToBeCloned" ) ;
40+ }
41+
42+ DataBind ( desireObjectToBeCloned , fieldType , null , false ) ;
43+ }
44+
45+ public FastDeepCloner ( object desireObjectToBeCloned , FieldType fieldType = FieldType . FieldInfo , Type ignorePropertiesWithAttribute = null , bool ? initPublicOnly = null )
46+ {
47+ if ( desireObjectToBeCloned == null )
48+ {
49+ throw new ArgumentNullException ( "desireObjectToBeCloned" ) ;
50+ }
51+
52+ DataBind ( desireObjectToBeCloned , fieldType , ignorePropertiesWithAttribute , initPublicOnly ) ;
53+ }
54+ #endregion
55+
56+ #region Public method clone
57+ /// <summary>
58+ /// Creates a new object that is a copy of the current instance.
59+ /// </summary>
60+ /// <returns>A new object that is a copy of this instance.</returns>
61+ public object Clone ( )
62+ {
63+ return DeepClone ( ) ;
64+ }
65+
66+ /// <summary>
67+ /// Creates a new object that is a copy of the current instance.
68+ /// </summary>
69+ /// <returns>A new object that is a copy of this instance.</returns>
70+ public T Clone < T > ( )
71+ {
72+ return ( T ) DeepClone ( ) ;
73+ }
74+ #endregion
75+
76+ #region Private method deep clone
77+ private void DataBind ( object desireObjectToBeCloned , FieldType fieldType = FieldType . FieldInfo , Type ignorePropertiesWithAttribute = null , bool ? initPublicOnly = null , IDictionary < string , bool > alreadyCloned = null )
78+ {
79+ if ( desireObjectToBeCloned == null )
80+ return ;
81+ if ( _cachedFields == null )
82+ _cachedFields = new Dictionary < Type , List < FieldInfo > > ( ) ;
83+ if ( _cachedPropertyInfo == null )
84+ _cachedPropertyInfo = new Dictionary < Type , List < PropertyInfo > > ( ) ;
85+
86+ _alreadyCloned = alreadyCloned ?? new Dictionary < string , bool > ( ) ;
87+ _ignorePropertiesWithAttribute = ignorePropertiesWithAttribute ;
88+ _primaryType = desireObjectToBeCloned . GetType ( ) ;
89+ _desireObjectToBeCloned = desireObjectToBeCloned ;
90+ _isArray = _primaryType . IsArray ;
91+ _initPublicOnly = initPublicOnly ;
92+ _fieldType = fieldType ;
93+
94+ if ( _isArray )
95+ {
96+ var array = ( Array ) desireObjectToBeCloned ;
97+ _length = array . Length ;
98+ _rank = array . Rank ;
99+ }
100+ else if ( ( desireObjectToBeCloned as IList ) != null )
101+ _isList = true ;
102+ else if ( typeof ( IDictionary ) . IsAssignableFrom ( _primaryType ) )
103+ _isDictionary = true ;
104+ }
105+
106+ /// <summary>
107+ /// Clone the object properties and its children recursively.
108+ /// </summary>
109+ /// <returns></returns>
110+ private object DeepClone ( )
111+ {
112+ if ( _desireObjectToBeCloned == null )
113+ return null ;
114+ // If the item is array of type more than one dimension then use Array.Clone
115+ if ( _isArray && _rank > 1 )
116+ return ( ( Array ) _desireObjectToBeCloned ) . Clone ( ) ;
117+
118+ object tObject ;
119+ // Clone IList or Array
120+ if ( _isArray || _isList )
121+ {
122+ tObject = _isArray ? Array . CreateInstance ( _primaryType . GetElementType ( ) , _length ) : Activator . CreateInstance ( typeof ( List < > ) . MakeGenericType ( _primaryType . GetProperties ( ) . Last ( ) . PropertyType ) ) ;
123+ var i = 0 ;
124+ foreach ( var item in ( IList ) _desireObjectToBeCloned )
125+ {
126+ object clonedIteam = null ;
127+ if ( item != null )
128+ {
129+ var underlyingSystemType = item . GetType ( ) . UnderlyingSystemType ;
130+ clonedIteam = ( item is string || ! underlyingSystemType . IsClass || IsInternalType ( underlyingSystemType ) )
131+ ? item
132+ : new FastDeepCloner ( item , _fieldType , _ignorePropertiesWithAttribute , _initPublicOnly , _alreadyCloned ) . DeepClone ( ) ;
133+ }
134+ if ( ! _isArray )
135+ ( ( IList ) tObject ) . Add ( clonedIteam ) ;
136+ else
137+ ( ( Array ) tObject ) . SetValue ( clonedIteam , i ) ;
138+
139+ i ++ ;
140+ }
141+ }
142+ else if ( _isDictionary ) // Clone IDictionary
143+ {
144+ tObject = Activator . CreateInstance ( _primaryType ) ;
145+ var dictionary = ( IDictionary ) _desireObjectToBeCloned ;
146+ foreach ( var key in dictionary . Keys )
147+ {
148+ var item = dictionary [ key ] ;
149+ object clonedIteam = null ;
150+ if ( item != null )
151+ {
152+ var underlyingSystemType = item . GetType ( ) . UnderlyingSystemType ;
153+ clonedIteam = ( item is string || ! underlyingSystemType . IsClass || IsInternalType ( underlyingSystemType ) )
154+ ? item
155+ : new FastDeepCloner ( item , _fieldType , _ignorePropertiesWithAttribute , _initPublicOnly , _alreadyCloned ) . DeepClone ( ) ;
156+ }
157+ ( ( IDictionary ) tObject ) . Add ( key , clonedIteam ) ;
158+ }
159+ }
160+ else
161+ {
162+ // Create an empty object and ignore its constructor.
163+ tObject = FormatterServices . GetUninitializedObject ( _primaryType ) ;
164+ var fullPath = _primaryType . Name ;
165+ if ( _fieldType == FieldType . PropertyInfo )
166+ {
167+ if ( ! _cachedPropertyInfo . ContainsKey ( _primaryType ) )
168+ {
169+ var properties = new List < PropertyInfo > ( ) ;
170+ if ( _primaryType . BaseType != null && _primaryType . BaseType . Name != "Object" )
171+ {
172+ properties . AddRange ( _primaryType . BaseType . GetProperties ( Binding ) ) ;
173+ properties . AddRange ( _primaryType . GetProperties ( Binding | BindingFlags . DeclaredOnly ) ) ;
174+ }
175+ else properties . AddRange ( _primaryType . GetProperties ( Binding ) ) ;
176+
177+ _cachedPropertyInfo . Add ( _primaryType , properties ) ;
178+ if ( _ignorePropertiesWithAttribute != null )
179+ _cachedPropertyInfo [ _primaryType ] . RemoveAll (
180+ x => x . GetCustomAttributes ( _ignorePropertiesWithAttribute , false ) . FirstOrDefault ( ) != null ) ;
181+ }
182+ }
183+ else if ( ! _cachedFields . ContainsKey ( _primaryType ) )
184+ {
185+ var properties = new List < FieldInfo > ( ) ;
186+ if ( _primaryType . BaseType != null && _primaryType . BaseType . Name != "Object" )
187+ {
188+ properties . AddRange ( _primaryType . BaseType . GetFields ( Binding ) ) ;
189+ properties . AddRange ( _primaryType . GetFields ( Binding | BindingFlags . DeclaredOnly ) ) ;
190+ }
191+ else properties . AddRange ( _primaryType . GetFields ( Binding ) ) ;
192+
193+ _cachedFields . Add ( _primaryType , properties ) ;
194+ if ( _ignorePropertiesWithAttribute != null )
195+ _cachedFields [ _primaryType ] . RemoveAll (
196+ x => x . GetCustomAttributes ( _ignorePropertiesWithAttribute , false ) . FirstOrDefault ( ) != null ) ;
197+ }
198+
199+ if ( _fieldType == FieldType . FieldInfo )
200+ {
201+ foreach ( var property in _cachedFields [ _primaryType ] )
202+ {
203+ // Validate if the property is a writable one.
204+ if ( property . IsInitOnly || property . FieldType == typeof ( System . IntPtr ) )
205+ continue ;
206+ if ( _initPublicOnly . HasValue && _initPublicOnly . Value && ! property . IsPublic )
207+ continue ;
208+ if ( _alreadyCloned . ContainsKey ( fullPath + property . Name ) )
209+ continue ;
210+ var value = property . GetValue ( _desireObjectToBeCloned ) ;
211+ if ( value == null )
212+ continue ;
213+
214+ if ( ! property . FieldType . IsClass || value is string )
215+ property . SetValue ( tObject , value ) ;
216+ else
217+ {
218+ _alreadyCloned . Add ( fullPath + property . Name , true ) ;
219+ property . SetValue ( tObject ,
220+ new FastDeepCloner ( value , _fieldType , _ignorePropertiesWithAttribute , _initPublicOnly ,
221+ _alreadyCloned ) . DeepClone ( ) ) ;
222+ }
223+ }
224+ }
225+ else
226+ {
227+ foreach ( var property in _cachedPropertyInfo [ _primaryType ] )
228+ {
229+ // Validate if the property is a writable one.
230+ if ( ! property . CanWrite || ! property . CanRead || property . PropertyType == typeof ( System . IntPtr ) )
231+ continue ;
232+ if ( _alreadyCloned . ContainsKey ( fullPath + property . Name ) )
233+ continue ;
234+ var value = property . GetValue ( _desireObjectToBeCloned , null ) ;
235+ if ( value == null )
236+ continue ;
237+
238+ if ( ! property . PropertyType . IsClass || value is string )
239+ property . SetValue ( tObject , value , null ) ;
240+ else
241+ {
242+ _alreadyCloned . Add ( fullPath + property . Name , true ) ;
243+ property . SetValue ( tObject ,
244+ new FastDeepCloner ( value , _fieldType , _ignorePropertiesWithAttribute , _initPublicOnly ,
245+ _alreadyCloned ) . DeepClone ( ) , null ) ;
246+ }
247+ }
248+ }
249+ }
250+
251+ return tObject ;
252+ }
253+ #endregion
254+
255+ private FastDeepCloner ( object desireObjectToBeCloned , FieldType fielType = FieldType . FieldInfo , Type ignorePropertiesWithAttribute = null , bool ? initPublicOnly = null , IDictionary < string , bool > alreadyCloned = null )
256+ {
257+ DataBind ( desireObjectToBeCloned , fielType , ignorePropertiesWithAttribute , initPublicOnly , alreadyCloned ) ;
258+ }
259+
260+ /// <summary>
261+ /// Determines if the specified type is an internal type.
262+ /// </summary>
263+ /// <param name="underlyingSystemType"></param>
264+ /// <returns><c>true</c> if type is internal, else <c>false</c>.</returns>
265+ private static bool IsInternalType ( Type underlyingSystemType )
266+ {
267+ return underlyingSystemType == typeof ( string ) ||
268+ underlyingSystemType == typeof ( decimal ) ||
269+ underlyingSystemType == typeof ( int ) ||
270+ underlyingSystemType == typeof ( double ) ||
271+ underlyingSystemType == typeof ( float ) ||
272+ underlyingSystemType == typeof ( bool ) ||
273+ underlyingSystemType == typeof ( long ) ||
274+ underlyingSystemType == typeof ( DateTime ) ||
275+ underlyingSystemType == typeof ( ushort ) ||
276+ underlyingSystemType == typeof ( short ) ||
277+ underlyingSystemType == typeof ( sbyte ) ||
278+ underlyingSystemType == typeof ( byte ) ||
279+ underlyingSystemType == typeof ( ulong ) ||
280+ underlyingSystemType == typeof ( uint ) ||
281+ underlyingSystemType == typeof ( char ) ||
282+ underlyingSystemType == typeof ( TimeSpan ) ;
283+ }
284+ }
285+
286+ public enum FieldType
287+ {
288+ FieldInfo ,
289+ PropertyInfo
290+ }
291+ }
0 commit comments