< Summary - Core.Tests

Information
Class: Common.Core.Classes.ReflectionHelper
Assembly: Common.Core
File(s): D:\a\NuGetPackages\NuGetPackages\src\Common\Core\Classes\ReflectionHelper.cs
Tag: 3_8508158812
Line coverage
100%
Covered lines: 172
Uncovered lines: 0
Coverable lines: 172
Total lines: 303
Line coverage: 100%
Branch coverage
86%
Covered branches: 93
Total branches: 108
Branch coverage: 86.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
ApplyChanges(...)71.42%1414100%
IsEqual(...)85.71%1414100%
IsEqualClass(...)50%66100%
CreateDeepCopy(...)92.85%1414100%
CopyProperties(...)100%1010100%
CopyFields(...)100%22100%
IsClassOrInterface(...)100%44100%
IsStringType(...)100%22100%
GetProperties(...)100%11100%
GetFields(...)100%11100%
ProcessCollection(...)83.33%66100%
ProcessArray(...)100%44100%
ProcessDictionary(...)87.5%88100%
ProcessList(...)83.33%66100%
IsList(...)87.5%88100%
IsDictionary(...)87.5%88100%
AddCurrentPath(...)100%22100%

File(s)

D:\a\NuGetPackages\NuGetPackages\src\Common\Core\Classes\ReflectionHelper.cs

#LineLine coverage
 1using System.Collections;
 2using System.Reflection;
 3using System.Security;
 4
 5namespace Common.Core.Classes;
 6
 7/// <summary>Class to provide methods that use reflection.</summary>
 8public static class ReflectionHelper
 9{
 110  private static readonly Type _stringType = typeof( string );
 111  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
 420  {
 421    if( source is null || target is null ) { return; }
 22
 8023    foreach( PropertyInfo prop in GetProperties( source.GetType() ) )
 3424    {
 4025      if( !prop.CanWrite ) {  continue; }
 26
 3127      object? sourceVal = prop.GetValue( source );
 3128      object? targetVal = prop.GetValue( target );
 29
 3130      if( IsClassOrInterface( prop ) )
 231      {
 232        if( sourceVal is null && targetVal is not null ) { target = source; }
 33        else
 234        {
 35          //if( sourceVal is not null && targetVal is null ) { target = ; }
 236          ApplyChanges( sourceVal, targetVal );
 237        }
 238      }
 39      else
 2940      {
 2941        prop.SetValue( target, sourceVal );
 2942      }
 3143    }
 444  }
 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
 5656  {
 5657    if( source is null || target is null ) { return false; }
 58
 64259    foreach( PropertyInfo prop in GetProperties( source.GetType() ) )
 25360    {
 27761      if( !prop.CanWrite ) continue;
 22962      object? sourceVal = prop.GetValue( source );
 22963      object? targetVal = prop.GetValue( target );
 64
 22965      if( IsClassOrInterface( prop ) )
 2466      {
 2467        return IsEqualClass( sourceVal, targetVal );
 68      }
 69      else
 20570      {
 22171        if( sourceVal is not null && !sourceVal.Equals( targetVal ) ) { return false; }
 19772      }
 19773    }
 74
 2475    return true;
 5676  }
 77
 78  private static bool IsEqualClass( object? sourceVal, object? targetVal )
 2479  {
 2480    if( sourceVal is null & targetVal is not null ||
 2481      sourceVal is not null & targetVal is null ) { return false; }
 2482    if( !IsEqual( sourceVal, targetVal ) ) { return false; }
 2483    return true;
 2484  }
 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 )
 38106  {
 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
 39110    if( obj == null ) return null;
 37111    Type typ = obj.GetType();
 112
 57113    if( typ == _stringType ) { return obj.ToString(); } // Handle strings differently
 27114    else if( typ == _secureType ) // Handle secure strings differently
 1115    {
 1116      return new System.Net.NetworkCredential( "", new System.Net.NetworkCredential( "",
 1117        (SecureString)obj ).Password ).SecurePassword;
 118    }
 30119    else if( typ.IsArray ) { return ProcessArray( obj ); }
 34120    else if( obj is IEnumerable or ICollection ) { return ProcessCollection( typ, obj ); }
 121
 122    // Try and create an instance of the object type
 19123    object? objCopy = null;
 57124    try { objCopy = Activator.CreateInstance( type: typ, nonPublic: true ); } catch( Exception ) { }
 19125    if( objCopy is null ) { return objCopy; }
 126
 61127    if( typ.IsClass ) { CopyProperties( typ, obj, objCopy ); } // For a class use the properties
 15128    else { CopyFields( typ, obj, objCopy ); } // For anything else use the fields
 129
 19130    return objCopy;
 38131  }
 132
 133  private static void CopyProperties( Type typ, object? obj, object? objCopy )
 14134  {
 272135    foreach( PropertyInfo prop in GetProperties( typ ) )
 115136    {
 135137      if( !prop.CanWrite ) { continue; }
 138
 105139      if( !prop.PropertyType.IsPrimitive && !prop.PropertyType.IsValueType &&
 105140        prop.PropertyType != _stringType )
 12141      {
 12142        object? fieldCopy = CreateDeepCopy( prop.GetValue( obj ) );
 12143        prop.SetValue( objCopy, fieldCopy );
 12144      }
 145      else
 93146      {
 93147        prop.SetValue( objCopy, prop.GetValue( obj ) );
 93148      }
 105149    }
 14150  }
 151
 152  private static void CopyFields( Type typ, object? obj, object? objCopy )
 5153  {
 5154    FieldInfo[] fields = GetFields( typ );
 25155    foreach( FieldInfo field in fields )
 5156    {
 5157      field.SetValue( objCopy, field.GetValue( obj ) );
 5158    }
 5159  }
 160
 161  #endregion
 162
 163  #region Private Functions
 164
 165  private static bool IsClassOrInterface( PropertyInfo prop )
 260166  {
 167    // String is classified as a class as is SecureString
 260168    return (prop.PropertyType.IsClass || prop.PropertyType.IsInterface ) && !IsStringType( prop );
 260169  }
 170
 171  private static bool IsStringType( PropertyInfo prop )
 222172  {
 173    // String is classified as a class as is SecureString
 222174    return prop.PropertyType == _stringType || prop.PropertyType == _secureType;
 222175  }
 176
 177  private static PropertyInfo[] GetProperties<T>( T type ) where T : Type
 74178  {
 179    // https://learn.microsoft.com/en-us/dotnet/api/system.reflection.propertyinfo
 180
 181    // Ignore static properties
 74182    return type.GetProperties( BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic );
 74183  }
 184
 185  private static FieldInfo[] GetFields<T>( T type ) where T : Type
 5186  {
 187    // https://learn.microsoft.com/en-us/dotnet/api/system.reflection.fieldinfo
 188
 189    // Ignore static fields
 5190    return type.GetFields( BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic );
 5191  }
 192
 193  private static object? ProcessCollection( Type typ, object obj )
 5194  {
 5195    bool isList = IsList( typ );
 5196    bool isDictionary = IsDictionary( typ );
 5197    if( !isList && !isDictionary ) return Activator.CreateInstance( typ );
 198
 5199    if( isList )
 2200    {
 2201      return ProcessList( typ, obj );
 202    }
 203
 3204    return ProcessDictionary( typ, obj );
 5205  }
 206
 207  private static Array? ProcessArray( object obj )
 2208  {
 2209    Array org = (Array)obj;
 2210    Array? ret = null;
 211
 2212    Type? elm = obj.GetType().GetElementType();
 2213    if( elm is not null )
 2214    {
 2215      ret = Array.CreateInstance( elm, org.Length );
 2216      Array cpy = ret;
 217
 14218      for( int i = 0; i < org.Length; i++ )
 5219      {
 5220        cpy.SetValue( CreateDeepCopy( org.GetValue( i ) ), i );
 5221      }
 2222    }
 223
 2224    return ret;
 2225  }
 226
 227  private static IDictionary? ProcessDictionary( Type typ, object obj )
 3228  {
 3229    IDictionary? org = (IDictionary)obj;
 3230    IDictionary? ret = (IDictionary)Activator.CreateInstance( typ )!;
 3231    if( org is not null && ret is not null )
 3232    {
 17233      foreach( object orgKey in org.Keys )
 4234      {
 4235        var orgVal = org[orgKey];
 4236        var cpyKey = CreateDeepCopy( orgKey );
 4237        var cpyVal = CreateDeepCopy( orgVal );
 238
 4239        if( cpyKey is not null )
 4240        {
 4241          ret.Add( cpyKey, cpyVal );
 4242        }
 4243      }
 3244    }
 245
 3246    return ret;
 247
 3248  }
 249
 250  private static IList? ProcessList( Type typ, object obj )
 2251  {
 2252    IList? org = (IList)obj;
 2253    IList? ret = (IList)Activator.CreateInstance( typ )!;
 2254    if( org is not null && ret is not null )
 2255    {
 14256      foreach( object? val in org )
 4257      {
 4258        ret.Add( CreateDeepCopy( val ) );
 4259      }
 2260    }
 261
 2262    return ret;
 2263  }
 264
 265  private static bool IsList( Type type )
 5266  {
 7267    if( typeof( IList ).IsAssignableFrom( type ) ) return true;
 268
 65269    foreach( Type it in type.GetInterfaces() )
 28270    {
 28271      if( it.IsGenericType && typeof( IList<> ) == it.GetGenericTypeDefinition() ) return true;
 28272    }
 273
 3274    return false;
 5275  }
 276
 277  private static bool IsDictionary( Type type )
 5278  {
 8279    if( typeof( IDictionary ).IsAssignableFrom( type ) ) return true;
 280
 38281    foreach( Type it in type.GetInterfaces() )
 16282    {
 16283      if( it.IsGenericType && typeof( IDictionary<,> ) == it.GetGenericTypeDefinition() ) return true;
 16284    }
 285
 2286    return false;
 5287  }
 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 = "" )
 3295  {
 3296    string path = Path.GetDirectoryName( Assembly.GetExecutingAssembly().Location )!;
 3297    if( !string.IsNullOrWhiteSpace( fileName ) )
 2298    {
 2299      path = Path.Combine( path, Path.GetFileName( fileName ) );
 2300    }
 3301    return path;
 3302  }
 303}