| | 1 | | using System.Collections; |
| | 2 | | using System.Reflection; |
| | 3 | | using System.Security; |
| | 4 | |
|
| | 5 | | namespace Common.Core.Classes; |
| | 6 | |
|
| | 7 | | /// <summary>Class to provide methods that use reflection.</summary> |
| | 8 | | public static class ReflectionHelper |
| | 9 | | { |
| 1 | 10 | | private static readonly Type _stringType = typeof( string ); |
| 1 | 11 | | private static readonly Type _secureType = typeof( SecureString ); |
| | 12 | |
|
| | 13 | | #region Apply Changes |
| | 14 | |
|
| | 15 | | /// <summary>Applies changes to the target object properties from the source object.</summary> |
| | 16 | | /// <typeparam name="T">Class type being used.</typeparam> |
| | 17 | | /// <param name="source">Source object.</param> |
| | 18 | | /// <param name="target">Target object.</param> |
| | 19 | | public static void ApplyChanges<T>( T? source, T? target ) where T : class |
| 4 | 20 | | { |
| 4 | 21 | | if( source is null || target is null ) { return; } |
| | 22 | |
|
| 80 | 23 | | foreach( PropertyInfo prop in GetProperties( source.GetType() ) ) |
| 34 | 24 | | { |
| 40 | 25 | | if( !prop.CanWrite ) { continue; } |
| | 26 | |
|
| 31 | 27 | | object? sourceVal = prop.GetValue( source ); |
| 31 | 28 | | object? targetVal = prop.GetValue( target ); |
| | 29 | |
|
| 31 | 30 | | if( IsClassOrInterface( prop ) ) |
| 2 | 31 | | { |
| 2 | 32 | | if( sourceVal is null && targetVal is not null ) { target = source; } |
| | 33 | | else |
| 2 | 34 | | { |
| | 35 | | //if( sourceVal is not null && targetVal is null ) { target = ; } |
| 2 | 36 | | ApplyChanges( sourceVal, targetVal ); |
| 2 | 37 | | } |
| 2 | 38 | | } |
| | 39 | | else |
| 29 | 40 | | { |
| 29 | 41 | | prop.SetValue( target, sourceVal ); |
| 29 | 42 | | } |
| 31 | 43 | | } |
| 4 | 44 | | } |
| | 45 | |
|
| | 46 | | #endregion |
| | 47 | |
|
| | 48 | | #region Check For Changes |
| | 49 | |
|
| | 50 | | /// <summary>Compare values to the target object properties from the source object.</summary> |
| | 51 | | /// <typeparam name="T">Class type being used.</typeparam> |
| | 52 | | /// <param name="source">Source object.</param> |
| | 53 | | /// <param name="target">Target object.</param> |
| | 54 | | /// <returns><see langword="true"/> if the source object properties are equal to the target object.</returns> |
| | 55 | | public static bool IsEqual<T>( T? source, T? target ) where T : class |
| 56 | 56 | | { |
| 56 | 57 | | if( source is null || target is null ) { return false; } |
| | 58 | |
|
| 642 | 59 | | foreach( PropertyInfo prop in GetProperties( source.GetType() ) ) |
| 253 | 60 | | { |
| 277 | 61 | | if( !prop.CanWrite ) continue; |
| 229 | 62 | | object? sourceVal = prop.GetValue( source ); |
| 229 | 63 | | object? targetVal = prop.GetValue( target ); |
| | 64 | |
|
| 229 | 65 | | if( IsClassOrInterface( prop ) ) |
| 24 | 66 | | { |
| 24 | 67 | | return IsEqualClass( sourceVal, targetVal ); |
| | 68 | | } |
| | 69 | | else |
| 205 | 70 | | { |
| 221 | 71 | | if( sourceVal is not null && !sourceVal.Equals( targetVal ) ) { return false; } |
| 197 | 72 | | } |
| 197 | 73 | | } |
| | 74 | |
|
| 24 | 75 | | return true; |
| 56 | 76 | | } |
| | 77 | |
|
| | 78 | | private static bool IsEqualClass( object? sourceVal, object? targetVal ) |
| 24 | 79 | | { |
| 24 | 80 | | if( sourceVal is null & targetVal is not null || |
| 24 | 81 | | sourceVal is not null & targetVal is null ) { return false; } |
| 24 | 82 | | if( !IsEqual( sourceVal, targetVal ) ) { return false; } |
| 24 | 83 | | return true; |
| 24 | 84 | | } |
| | 85 | |
|
| | 86 | | #endregion |
| | 87 | |
|
| | 88 | | #region Create Deep Copy |
| | 89 | |
|
| | 90 | | /// <summary>Creates a deep copy of an object.</summary> |
| | 91 | | /// <param name="obj">Object to copy.</param> |
| | 92 | | /// <returns><see langword="null"/> is returned if the object could not be copied.</returns> |
| | 93 | | /// <remarks> |
| | 94 | | /// When a deep copy operation is performed, the cloned object can be modified |
| | 95 | | /// without affecting the original object. |
| | 96 | | /// <br/><br/>The <i>Object.MemberwiseClone</i> method creates a shallow copy by creating a new object, |
| | 97 | | /// and then copying the non-static fields of the current object to the new object. |
| | 98 | | /// If a field is a value type, a bit-by-bit copy of the field is performed. |
| | 99 | | /// If a field is a reference type, the reference is copied but the referred object |
| | 100 | | /// is not; therefore, the original object and its clone refer to the same object. |
| | 101 | | /// <br/><br/>This method is meant for simple Model classes, more complex classes are |
| | 102 | | /// supported but because it is written using reflection it can be somewhat slow and cause |
| | 103 | | /// performance issues. |
| | 104 | | /// </remarks> |
| | 105 | | public static object? CreateDeepCopy( object? obj ) |
| 38 | 106 | | { |
| | 107 | | // https://stackoverflow.com/questions/3647048/create-a-deep-copy-in-c-sharp |
| | 108 | | // https://codereview.stackexchange.com/questions/147856/generic-null-empty-check-for-each-property-of-a-class |
| | 109 | |
|
| 39 | 110 | | if( obj == null ) return null; |
| 37 | 111 | | Type typ = obj.GetType(); |
| | 112 | |
|
| 57 | 113 | | if( typ == _stringType ) { return obj.ToString(); } // Handle strings differently |
| 27 | 114 | | else if( typ == _secureType ) // Handle secure strings differently |
| 1 | 115 | | { |
| 1 | 116 | | return new System.Net.NetworkCredential( "", new System.Net.NetworkCredential( "", |
| 1 | 117 | | (SecureString)obj ).Password ).SecurePassword; |
| | 118 | | } |
| 30 | 119 | | else if( typ.IsArray ) { return ProcessArray( obj ); } |
| 34 | 120 | | else if( obj is IEnumerable or ICollection ) { return ProcessCollection( typ, obj ); } |
| | 121 | |
|
| | 122 | | // Try and create an instance of the object type |
| 19 | 123 | | object? objCopy = null; |
| 57 | 124 | | try { objCopy = Activator.CreateInstance( type: typ, nonPublic: true ); } catch( Exception ) { } |
| 19 | 125 | | if( objCopy is null ) { return objCopy; } |
| | 126 | |
|
| 61 | 127 | | if( typ.IsClass ) { CopyProperties( typ, obj, objCopy ); } // For a class use the properties |
| 15 | 128 | | else { CopyFields( typ, obj, objCopy ); } // For anything else use the fields |
| | 129 | |
|
| 19 | 130 | | return objCopy; |
| 38 | 131 | | } |
| | 132 | |
|
| | 133 | | private static void CopyProperties( Type typ, object? obj, object? objCopy ) |
| 14 | 134 | | { |
| 272 | 135 | | foreach( PropertyInfo prop in GetProperties( typ ) ) |
| 115 | 136 | | { |
| 135 | 137 | | if( !prop.CanWrite ) { continue; } |
| | 138 | |
|
| 105 | 139 | | if( !prop.PropertyType.IsPrimitive && !prop.PropertyType.IsValueType && |
| 105 | 140 | | prop.PropertyType != _stringType ) |
| 12 | 141 | | { |
| 12 | 142 | | object? fieldCopy = CreateDeepCopy( prop.GetValue( obj ) ); |
| 12 | 143 | | prop.SetValue( objCopy, fieldCopy ); |
| 12 | 144 | | } |
| | 145 | | else |
| 93 | 146 | | { |
| 93 | 147 | | prop.SetValue( objCopy, prop.GetValue( obj ) ); |
| 93 | 148 | | } |
| 105 | 149 | | } |
| 14 | 150 | | } |
| | 151 | |
|
| | 152 | | private static void CopyFields( Type typ, object? obj, object? objCopy ) |
| 5 | 153 | | { |
| 5 | 154 | | FieldInfo[] fields = GetFields( typ ); |
| 25 | 155 | | foreach( FieldInfo field in fields ) |
| 5 | 156 | | { |
| 5 | 157 | | field.SetValue( objCopy, field.GetValue( obj ) ); |
| 5 | 158 | | } |
| 5 | 159 | | } |
| | 160 | |
|
| | 161 | | #endregion |
| | 162 | |
|
| | 163 | | #region Private Functions |
| | 164 | |
|
| | 165 | | private static bool IsClassOrInterface( PropertyInfo prop ) |
| 260 | 166 | | { |
| | 167 | | // String is classified as a class as is SecureString |
| 260 | 168 | | return (prop.PropertyType.IsClass || prop.PropertyType.IsInterface ) && !IsStringType( prop ); |
| 260 | 169 | | } |
| | 170 | |
|
| | 171 | | private static bool IsStringType( PropertyInfo prop ) |
| 222 | 172 | | { |
| | 173 | | // String is classified as a class as is SecureString |
| 222 | 174 | | return prop.PropertyType == _stringType || prop.PropertyType == _secureType; |
| 222 | 175 | | } |
| | 176 | |
|
| | 177 | | private static PropertyInfo[] GetProperties<T>( T type ) where T : Type |
| 74 | 178 | | { |
| | 179 | | // https://learn.microsoft.com/en-us/dotnet/api/system.reflection.propertyinfo |
| | 180 | |
|
| | 181 | | // Ignore static properties |
| 74 | 182 | | return type.GetProperties( BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic ); |
| 74 | 183 | | } |
| | 184 | |
|
| | 185 | | private static FieldInfo[] GetFields<T>( T type ) where T : Type |
| 5 | 186 | | { |
| | 187 | | // https://learn.microsoft.com/en-us/dotnet/api/system.reflection.fieldinfo |
| | 188 | |
|
| | 189 | | // Ignore static fields |
| 5 | 190 | | return type.GetFields( BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic ); |
| 5 | 191 | | } |
| | 192 | |
|
| | 193 | | private static object? ProcessCollection( Type typ, object obj ) |
| 5 | 194 | | { |
| 5 | 195 | | bool isList = IsList( typ ); |
| 5 | 196 | | bool isDictionary = IsDictionary( typ ); |
| 5 | 197 | | if( !isList && !isDictionary ) return Activator.CreateInstance( typ ); |
| | 198 | |
|
| 5 | 199 | | if( isList ) |
| 2 | 200 | | { |
| 2 | 201 | | return ProcessList( typ, obj ); |
| | 202 | | } |
| | 203 | |
|
| 3 | 204 | | return ProcessDictionary( typ, obj ); |
| 5 | 205 | | } |
| | 206 | |
|
| | 207 | | private static Array? ProcessArray( object obj ) |
| 2 | 208 | | { |
| 2 | 209 | | Array org = (Array)obj; |
| 2 | 210 | | Array? ret = null; |
| | 211 | |
|
| 2 | 212 | | Type? elm = obj.GetType().GetElementType(); |
| 2 | 213 | | if( elm is not null ) |
| 2 | 214 | | { |
| 2 | 215 | | ret = Array.CreateInstance( elm, org.Length ); |
| 2 | 216 | | Array cpy = ret; |
| | 217 | |
|
| 14 | 218 | | for( int i = 0; i < org.Length; i++ ) |
| 5 | 219 | | { |
| 5 | 220 | | cpy.SetValue( CreateDeepCopy( org.GetValue( i ) ), i ); |
| 5 | 221 | | } |
| 2 | 222 | | } |
| | 223 | |
|
| 2 | 224 | | return ret; |
| 2 | 225 | | } |
| | 226 | |
|
| | 227 | | private static IDictionary? ProcessDictionary( Type typ, object obj ) |
| 3 | 228 | | { |
| 3 | 229 | | IDictionary? org = (IDictionary)obj; |
| 3 | 230 | | IDictionary? ret = (IDictionary)Activator.CreateInstance( typ )!; |
| 3 | 231 | | if( org is not null && ret is not null ) |
| 3 | 232 | | { |
| 17 | 233 | | foreach( object orgKey in org.Keys ) |
| 4 | 234 | | { |
| 4 | 235 | | var orgVal = org[orgKey]; |
| 4 | 236 | | var cpyKey = CreateDeepCopy( orgKey ); |
| 4 | 237 | | var cpyVal = CreateDeepCopy( orgVal ); |
| | 238 | |
|
| 4 | 239 | | if( cpyKey is not null ) |
| 4 | 240 | | { |
| 4 | 241 | | ret.Add( cpyKey, cpyVal ); |
| 4 | 242 | | } |
| 4 | 243 | | } |
| 3 | 244 | | } |
| | 245 | |
|
| 3 | 246 | | return ret; |
| | 247 | |
|
| 3 | 248 | | } |
| | 249 | |
|
| | 250 | | private static IList? ProcessList( Type typ, object obj ) |
| 2 | 251 | | { |
| 2 | 252 | | IList? org = (IList)obj; |
| 2 | 253 | | IList? ret = (IList)Activator.CreateInstance( typ )!; |
| 2 | 254 | | if( org is not null && ret is not null ) |
| 2 | 255 | | { |
| 14 | 256 | | foreach( object? val in org ) |
| 4 | 257 | | { |
| 4 | 258 | | ret.Add( CreateDeepCopy( val ) ); |
| 4 | 259 | | } |
| 2 | 260 | | } |
| | 261 | |
|
| 2 | 262 | | return ret; |
| 2 | 263 | | } |
| | 264 | |
|
| | 265 | | private static bool IsList( Type type ) |
| 5 | 266 | | { |
| 7 | 267 | | if( typeof( IList ).IsAssignableFrom( type ) ) return true; |
| | 268 | |
|
| 65 | 269 | | foreach( Type it in type.GetInterfaces() ) |
| 28 | 270 | | { |
| 28 | 271 | | if( it.IsGenericType && typeof( IList<> ) == it.GetGenericTypeDefinition() ) return true; |
| 28 | 272 | | } |
| | 273 | |
|
| 3 | 274 | | return false; |
| 5 | 275 | | } |
| | 276 | |
|
| | 277 | | private static bool IsDictionary( Type type ) |
| 5 | 278 | | { |
| 8 | 279 | | if( typeof( IDictionary ).IsAssignableFrom( type ) ) return true; |
| | 280 | |
|
| 38 | 281 | | foreach( Type it in type.GetInterfaces() ) |
| 16 | 282 | | { |
| 16 | 283 | | if( it.IsGenericType && typeof( IDictionary<,> ) == it.GetGenericTypeDefinition() ) return true; |
| 16 | 284 | | } |
| | 285 | |
|
| 2 | 286 | | return false; |
| 5 | 287 | | } |
| | 288 | |
|
| | 289 | | #endregion |
| | 290 | |
|
| | 291 | | /// <summary>Adds the current path to a file name.</summary> |
| | 292 | | /// <param name="fileName">The file name to use.</param> |
| | 293 | | /// <returns>The full file name, or if no file name is supplied, the executing path is returned.</returns> |
| | 294 | | public static string AddCurrentPath( string fileName = "" ) |
| 3 | 295 | | { |
| 3 | 296 | | string path = Path.GetDirectoryName( Assembly.GetExecutingAssembly().Location )!; |
| 3 | 297 | | if( !string.IsNullOrWhiteSpace( fileName ) ) |
| 2 | 298 | | { |
| 2 | 299 | | path = Path.Combine( path, Path.GetFileName( fileName ) ); |
| 2 | 300 | | } |
| 3 | 301 | | return path; |
| 3 | 302 | | } |
| | 303 | | } |