using System.Diagnostics.CodeAnalysis; using System.Reflection; using JetBrains.Annotations; using RockLib.Reflection.Optimized; namespace StabilityMatrix.Core.Extensions; [PublicAPI] public static class ObjectExtensions { /// /// Cache of Types to named field getters /// private static readonly Dictionary>> FieldGetterTypeCache = new(); /// /// Cache of Types to named field setters /// private static readonly Dictionary>> FieldSetterTypeCache = new(); /// /// Cache of Types to named property getters /// private static readonly Dictionary>> PropertyGetterTypeCache = new(); /// /// Get the value of a named private field from an object /// /// /// The field must be defined by the runtime type of or its first base type. /// For higher inheritance levels, use to specify the exact defining type. /// public static T? GetPrivateField(this object obj, string fieldName) { // Check cache var fieldGetterCache = FieldGetterTypeCache.GetOrAdd(obj.GetType()); if (!fieldGetterCache.TryGetValue(fieldName, out var fieldGetter)) { // Get the field var field = obj.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic); // Try get from parent field ??= obj.GetType().BaseType?.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic); if (field is null) { throw new ArgumentException($"Field {fieldName} not found on type {obj.GetType().Name}"); } // Create a getter for the field fieldGetter = field.CreateGetter(); // Add to cache fieldGetterCache.Add(fieldName, fieldGetter); } return (T?)fieldGetter(obj); } /// /// Get the value of a protected property from an object /// /// /// The property must be defined by the runtime type of or its first base type. /// public static object? GetProtectedProperty(this object obj, [LocalizationRequired(false)] string propertyName) { // Check cache var fieldGetterCache = PropertyGetterTypeCache.GetOrAdd(obj.GetType()); if (!fieldGetterCache.TryGetValue(propertyName, out var propertyGetter)) { // Get the field var propertyInfo = obj.GetType() .GetProperty(propertyName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); // Try get from parent propertyInfo ??= obj.GetType() .BaseType ?.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); if (propertyInfo is null) { throw new ArgumentException($"Property {propertyName} not found on type {obj.GetType().Name}"); } // Create a getter for the field propertyGetter = o => propertyInfo.GetValue(o)!; // Add to cache fieldGetterCache.Add(propertyName, propertyGetter); } return (object?)propertyGetter(obj); } /// /// Get the value of a protected property from an object /// /// /// The property must be defined by the runtime type of or its first base type. /// public static T? GetProtectedProperty(this object obj, [LocalizationRequired(false)] string propertyName) where T : class { return (T?)GetProtectedProperty(obj, propertyName); } /// /// Get the value of a named private field from an object /// /// Type of the object that defines the field, must be a base class of /// Type of the field public static T? GetPrivateField(this TObject obj, string fieldName) where TObject : class { // Check cache var fieldGetterCache = FieldGetterTypeCache.GetOrAdd(typeof(TObject)); if (!fieldGetterCache.TryGetValue(fieldName, out var fieldGetter)) { // Get the field var field = typeof(TObject).GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic); if (field is null) { throw new ArgumentException( $"Field {typeof(TObject).Name}.{fieldName} not found on type {obj.GetType().Name}" ); } // Create a getter for the field fieldGetter = field.CreateGetter(); // Add to cache fieldGetterCache.Add(fieldName, fieldGetter); } return (T?)fieldGetter(obj); } /// /// Set the value of a named private field on an object /// public static void SetPrivateField(this object obj, string fieldName, object value) { // Check cache var fieldSetterCache = FieldSetterTypeCache.GetOrAdd(obj.GetType()); if (!fieldSetterCache.TryGetValue(fieldName, out var fieldSetter)) { // Get the field var field = obj.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic); // Try get from parent field ??= obj.GetType().BaseType?.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic); if (field is null) { throw new ArgumentException($"Field {fieldName} not found on type {obj.GetType().Name}"); } // Create a setter for the field fieldSetter = field.CreateSetter(); // Add to cache fieldSetterCache.Add(fieldName, fieldSetter); } fieldSetter(obj, value); } /// /// Set the value of a named private field on an object /// public static void SetPrivateField(this object obj, string fieldName, T? value) { // Check cache var fieldSetterCache = FieldSetterTypeCache.GetOrAdd(obj.GetType()); if (!fieldSetterCache.TryGetValue(fieldName, out var fieldSetter)) { // Get the field var field = obj.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic); // Try get from parent field ??= obj.GetType().BaseType?.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic); if (field is null) { throw new ArgumentException($"Field {fieldName} not found on type {obj.GetType().Name}"); } // Create a setter for the field fieldSetter = field.CreateSetter(); // Add to cache fieldSetterCache.Add(fieldName, fieldSetter); } fieldSetter(obj, value!); } }