Skip to content

Commit

Permalink
feat: add ReflectionHelper.GetPrivateField<T>
Browse files Browse the repository at this point in the history
  • Loading branch information
davidkallesen committed Oct 10, 2024
1 parent 55ea945 commit 890698e
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 21 deletions.
19 changes: 15 additions & 4 deletions docs/CodeDoc/Atc/Atc.Helpers.md
Original file line number Diff line number Diff line change
Expand Up @@ -1945,16 +1945,27 @@ ReflectionHelper.
### Static Methods
#### GetPrivateField
>```csharp
>T GetPrivateField(object target, string fieldName)
>```
><b>Summary:</b> Gets the value of a private field from the specified target object.
>
><b>Parameters:</b><br>
>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`target`&nbsp;&nbsp;-&nbsp;&nbsp;The target object containing the private field.<br />
>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`fieldName`&nbsp;&nbsp;-&nbsp;&nbsp;The name of the private field to retrieve.<br />
>
><b>Returns:</b> The value of the private field, cast to the specified type.
#### SetPrivateField
>```csharp
>void SetPrivateField(object target, string fieldName, object value)
>```
><b>Summary:</b> Sets the private field.
><b>Summary:</b> Sets the value of a private field on the specified target object.
>
><b>Parameters:</b><br>
>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`target`&nbsp;&nbsp;-&nbsp;&nbsp;The target.<br />
>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`fieldName`&nbsp;&nbsp;-&nbsp;&nbsp;Name of the field.<br />
>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`value`&nbsp;&nbsp;-&nbsp;&nbsp;The value.<br />
>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`target`&nbsp;&nbsp;-&nbsp;&nbsp;The target object containing the private field.<br />
>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`fieldName`&nbsp;&nbsp;-&nbsp;&nbsp;The name of the private field to set.<br />
>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`value`&nbsp;&nbsp;-&nbsp;&nbsp;The value to set on the private field.<br />
<br />
Expand Down
1 change: 1 addition & 0 deletions docs/CodeDoc/Atc/IndexExtended.md
Original file line number Diff line number Diff line change
Expand Up @@ -4669,6 +4669,7 @@
- KillEntryCaller(int timeoutInSec = 30)
- [ReflectionHelper](Atc.Helpers.md#reflectionhelper)
- Static Methods
- GetPrivateField(object target, string fieldName)
- SetPrivateField(object target, string fieldName, object value)
- [RegionInfoHelper](Atc.Helpers.md#regioninfohelper)
- Static Methods
Expand Down
80 changes: 63 additions & 17 deletions src/Atc/Helpers/ReflectionHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ namespace Atc.Helpers;
public static class ReflectionHelper
{
/// <summary>
/// Sets the private field.
/// Sets the value of a private field on the specified target object.
/// </summary>
/// <param name="target">The target.</param>
/// <param name="fieldName">Name of the field.</param>
/// <param name="value">The value.</param>
[SuppressMessage("Major Code Smell", "S3011:Reflection should not be used to increase accessibility of classes, methods, or fields", Justification = "OK.")]
/// <param name="target">The target object containing the private field.</param>
/// <param name="fieldName">The name of the private field to set.</param>
/// <param name="value">The value to set on the private field.</param>
/// <exception cref="ArgumentNullException">Thrown if the target or field name is null.</exception>
/// <exception cref="ArgumentException">Thrown if the private field is not found in the object hierarchy.</exception>
[SuppressMessage("Major Code Smell", "S3011:Reflection should not be used to increase accessibility of classes, methods, or fields", Justification = "Reflection required for testing private fields.")]
[ExcludeFromCodeCoverage]
public static void SetPrivateField(object target, string fieldName, object value)
{
Expand All @@ -22,27 +24,71 @@ public static void SetPrivateField(object target, string fieldName, object value

if (string.IsNullOrEmpty(fieldName))
{
throw new ArgumentNullOrDefaultException(nameof(fieldName));
throw new ArgumentException("Field name cannot be null or empty", nameof(fieldName));
}

var fieldInfo = GetFieldInfo(target, fieldName);
if (fieldInfo is null)
{
throw new ArgumentPropertyException($"Field '{fieldName}' not found in type '{target.GetType()}' or its base types.");
}

fieldInfo.SetValue(target, value);
}

/// <summary>
/// Gets the value of a private field from the specified target object.
/// </summary>
/// <typeparam name="T">The type of the field value.</typeparam>
/// <param name="target">The target object containing the private field.</param>
/// <param name="fieldName">The name of the private field to retrieve.</param>
/// <returns>The value of the private field, cast to the specified type.</returns>
/// <exception cref="ArgumentNullException">Thrown if the target or field name is null.</exception>
/// <exception cref="ArgumentException">Thrown if the private field is not found in the object hierarchy.</exception>
[SuppressMessage("Major Code Smell", "S3011:Reflection should not be used to increase accessibility of classes, methods, or fields", Justification = "Reflection required for testing private fields.")]
[ExcludeFromCodeCoverage]
public static T? GetPrivateField<T>(object target, string fieldName)
{
if (target is null)
{
throw new ArgumentNullException(nameof(target));
}

if (string.IsNullOrEmpty(fieldName))
{
throw new ArgumentException("Field name cannot be null or empty", nameof(fieldName));
}

var fieldInfo = GetFieldInfo(target, fieldName);
if (fieldInfo is null)
{
throw new ArgumentPropertyException($"Field '{fieldName}' not found in type '{target.GetType()}' or its base types.");
}

return (T?)fieldInfo.GetValue(target);
}

/// <summary>
/// Retrieves FieldInfo for a private field by name, traversing the inheritance hierarchy.
/// </summary>
/// <param name="target">The target object containing the private field.</param>
/// <param name="fieldName">The name of the private field.</param>
/// <returns>FieldInfo object representing the field, or null if not found.</returns>
[SuppressMessage("Major Code Smell", "S3011:Make sure that this accessibility bypass is safe here", Justification = "OK.")]
private static FieldInfo? GetFieldInfo(object target, string fieldName)
{
var type = target.GetType();
FieldInfo? fi = null;
while (type is not null)
{
fi = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
if (fi is not null)
var fieldInfo = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
if (fieldInfo != null)
{
break;
return fieldInfo;
}

type = type.BaseType!;
}

if (fi is null)
{
throw new ArgumentPropertyException($"Field '{fieldName}' not found in type hierarchy.");
type = type.BaseType;
}

fi.SetValue(target, value);
return null;
}
}

0 comments on commit 890698e

Please sign in to comment.