diff --git a/IPA.Loader/IPA.Loader.csproj b/IPA.Loader/IPA.Loader.csproj index 714bc209..4aebd238 100644 --- a/IPA.Loader/IPA.Loader.csproj +++ b/IPA.Loader/IPA.Loader.csproj @@ -138,6 +138,7 @@ + diff --git a/IPA.Loader/Utilities/Accessor.cs b/IPA.Loader/Utilities/Accessor.cs new file mode 100644 index 00000000..d3a5a42a --- /dev/null +++ b/IPA.Loader/Utilities/Accessor.cs @@ -0,0 +1,236 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; + +namespace IPA.Utilities +{ + /// + /// A type containing utilities for accessing non-public fields of objects. + /// + /// the type that the fields are on + /// the type of the field to access + /// + public static class FieldAccessor + { + /// + /// A delegate for a field accessor taking a ref and returning a ref. + /// + /// the object to access the field of + /// a reference to the field's value + public delegate ref U Accessor(ref T obj); + + // field name -> accessor + private static readonly Dictionary accessors = new Dictionary(); + + private static Accessor MakeAccessor(string fieldName) + { + var field = typeof(T).GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly); + if (field == null) + throw new MissingFieldException(typeof(T).Name, fieldName); + + var dynMethodName = $"<>_accessor__{fieldName}"; + // unfortunately DynamicMethod doesn't like having a ByRef return type, so reflection it + var dyn = new DynamicMethod(dynMethodName, typeof(U), new[] { typeof(T).MakeByRefType() }, typeof(FieldAccessor), true); + ReflectionUtil.DynamicMethodReturnType.SetValue(dyn, typeof(U).MakeByRefType()); + + var il = dyn.GetILGenerator(); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldflda, field); + il.Emit(OpCodes.Ret); + + return (Accessor)dyn.CreateDelegate(typeof(Accessor)); + } + + /// + /// Gets an for the field named on . + /// + /// the field name + /// an accessor for the field + /// if the field does not exist on + public static Accessor GetAccessor(string name) + { + if (!accessors.TryGetValue(name, out var accessor)) + accessors.Add(name, accessor = MakeAccessor(name)); + return accessor; + } + + /// + /// Accesses a field for an object by name. + /// + /// the object to access the field of + /// the name of the field to access + /// a reference to the object at the field + /// if the field does not exist on + /// + public static ref U Access(ref T obj, string name) => ref GetAccessor(name)(ref obj); + /// + /// Gets the value of a field of an object by name. + /// + /// + /// The only good reason to use this over is when you are working with a value type, + /// as it prevents a copy. + /// + /// the object to access the field of + /// the name of the field to access + /// the value of the field + /// if the field does not exist on + /// + /// + /// + public static U Get(ref T obj, string name) => Access(ref obj, name); + /// + /// Gets the value of a field of an object by name. + /// + /// the object to access the field of + /// the name of the field to access + /// the value of the field + /// if the field does not exist on + /// + /// + /// + public static U Get(T obj, string name) => Get(ref obj, name); + /// + /// Sets the value of a field for an object by name. + /// + /// + /// This overload must be used for value types. + /// + /// the object to set the field of + /// the name of the field + /// the value to set it to + /// if the field does not exist on + /// + /// + /// + public static void Set(ref T obj, string name, U value) => Access(ref obj, name) = value; + /// + /// Sets the value of a field for an object by name. + /// + /// + /// This overload cannot be safely used for value types. Use instead. + /// + /// the object to set the field of + /// the name of the field + /// the value to set it to + /// if the field does not exist on + /// + /// + /// + public static void Set(T obj, string name, U value) => Set(ref obj, name, value); + } + + /// + /// A type containing utilities for accessing non-public properties of an object. + /// + /// the type that the properties are on + /// the type of the property to access + public static class PropertyAccessor + { + /// + /// A getter for a property. + /// + /// the object it is a member of + /// the value of the property + public delegate U Getter(T obj); + /// + /// A setter for a property. + /// + /// the object it is a member of + /// the new property value + public delegate void Setter(T obj, U val); + + private static readonly Dictionary props = new Dictionary(); + + private static (Getter, Setter) MakeAccessors(string propName) + { + var prop = typeof(T).GetProperty(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly); + if (prop == null) + throw new MissingMemberException(typeof(T).Name, propName); + + var getM = prop.GetGetMethod(); + var setM = prop.GetSetMethod(); + Getter getter = null; + Setter setter = null; + if (getM != null) + getter = (Getter)Delegate.CreateDelegate(typeof(Getter), getM); + if (setM != null) + setter = (Setter)Delegate.CreateDelegate(typeof(Setter), setM); + + return (getter, setter); + } + + private static (Getter get, Setter set) GetAccessors(string propName) + { + if (!props.TryGetValue(propName, out var access)) + props.Add(propName, access = MakeAccessors(propName)); + return access; + } + + /// + /// Gets a for the property identified by . + /// + /// the name of the property + /// a that can access that property + /// when the property does not exist + public static Getter GetGetter(string name) => GetAccessors(name).get; + /// + /// Gets a for the property identified by . + /// + /// the name of the property + /// a that can access that property + /// when the property does not exist + public static Setter GetSetter(string name) => GetAccessors(name).set; + + /// + /// Gets the value of the property identified by on . + /// + /// + /// The only reason to use this over is if you are using a value type because + /// it avoids a copy. + /// + /// the instance to access + /// the name of the property + /// the value of the property + /// when the property does not exist + /// + /// + public static U Get(ref T obj, string name) => GetGetter(name)(obj); + /// + /// Gets the value of the property identified by on . + /// + /// the instance to access + /// the name of the property + /// the value of the property + /// when the property does not exist + /// + /// + public static U Get(T obj, string name) => GetGetter(name)(obj); + /// + /// Sets the value of the property identified by on . + /// + /// + /// This overload must be used for value types. + /// + /// the instance to access + /// the name of the property + /// the new value of the property + /// when the property does not exist + /// + /// + public static void Set(ref T obj, string name, U val) => GetSetter(name)(obj, val); + /// + /// Sets the value of the property identified by on . + /// + /// + /// This overload cannot be safely used for value types. Use instead. + /// + /// the instance to access + /// the name of the property + /// the new value of the property + /// when the property does not exist + /// + /// + public static void Set(T obj, string name, U val) => GetSetter(name)(obj, val); + } +} diff --git a/IPA.Loader/Utilities/ReflectionUtil.cs b/IPA.Loader/Utilities/ReflectionUtil.cs index 4ab49312..b47f6c1c 100644 --- a/IPA.Loader/Utilities/ReflectionUtil.cs +++ b/IPA.Loader/Utilities/ReflectionUtil.cs @@ -1,5 +1,6 @@ using System; using System.Reflection; +using System.Reflection.Emit; using UnityEngine; namespace IPA.Utilities @@ -7,52 +8,23 @@ namespace IPA.Utilities /// /// A utility class providing reflection helper methods. /// - public static class ReflectionUtil + public static partial class ReflectionUtil { - /// - /// Sets a field on the target object. - /// - /// the object instance - /// the field to set - /// the value to set it to - /// if does not exist on the runtime type of - public static void SetField(this object obj, string fieldName, object value) - { - var prop = obj.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); - if (prop == null) throw new ArgumentException($"Field {fieldName} does not exist", nameof(fieldName)); - prop?.SetValue(obj, value); - } + internal static readonly FieldInfo DynamicMethodReturnType = + typeof(DynamicMethod).GetField("returnType", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly); /// /// Sets a field on the target object, as gotten from . /// /// the type to get the field from + /// the type of the field to set /// the object instance /// the field to set /// the value to set it to - /// if does not exist on - public static void SetField(this T obj, string fieldName, object value) - { - var prop = typeof(T).GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); - if (prop == null) throw new ArgumentException($"Field {fieldName} does not exist", nameof(fieldName)); - prop?.SetValue(obj, value); - } - - /// - /// Gets the value of a field. - /// - /// the type of the field (result casted) - /// the object instance to pull from - /// the name of the field to read - /// the value of the field - /// if does not exist on the runtime type of - public static T GetField(this object obj, string fieldName) - { - var prop = obj.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance); - if (prop == null) throw new ArgumentException($"Field {fieldName} does not exist", nameof(fieldName)); - var value = prop?.GetValue(obj); - return (T) value; - } + /// if does not exist on + /// + public static void SetField(this T obj, string fieldName, U value) + => FieldAccessor.Set(ref obj, fieldName, value); /// /// Gets the value of a field. @@ -62,58 +34,36 @@ namespace IPA.Utilities /// the object instance to pull from /// the name of the field to read /// the value of the field - /// if does not exist on + /// if does not exist on + /// public static T GetField(this U obj, string fieldName) - { - var prop = typeof(U).GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance); - if (prop == null) throw new ArgumentException($"Field {fieldName} does not exist", nameof(fieldName)); - var value = prop?.GetValue(obj); - return (T)value; - } - - /// - /// Sets a property on the target object. - /// - /// the target object instance - /// the name of the property - /// the value to set it to - /// if does not exist on the runtime type of - public static void SetProperty(this object obj, string propertyName, object value) - { - var prop = obj.GetType().GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); - if (prop == null) throw new ArgumentException($"Property {propertyName} does not exist", nameof(propertyName)); - prop?.SetValue(obj, value, null); - } + => FieldAccessor.Get(ref obj, fieldName); /// - /// Sets a property on the target object, as gotten from + /// Sets a property on the target object, as gotten from . /// /// the type to get the property from + /// the type of the property to set /// the object instance /// the property to set /// the value to set it to - /// if does not exist on - public static void SetProperty(this T obj, string propertyName, object value) - { - var prop = typeof(T).GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); - if (prop == null) throw new ArgumentException($"Property {propertyName} does not exist", nameof(propertyName)); - prop?.SetValue(obj, value, null); - } + /// if does not exist on + /// + public static void SetProperty(this T obj, string propertyName, U value) + => PropertyAccessor.Set(ref obj, propertyName, value); /// - /// Invokes a method on an object. + /// Gets a property on the target object, as gotten from . /// - /// the object to call from - /// the method name - /// the method arguments - /// the return value - /// if does not exist on the runtime type of - public static object InvokeMethod(this object obj, string methodName, params object[] methodArgs) - { - MethodInfo dynMethod = obj.GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); - if (dynMethod == null) throw new ArgumentException($"Method {methodName} does not exist", nameof(methodName)); - return dynMethod?.Invoke(obj, methodArgs); - } + /// the type to get the property from + /// the type of the property to get + /// the object instance + /// the property to get + /// the value of the property + /// if does not exist on + /// + public static U GetProperty(this T obj, string propertyName) + => PropertyAccessor.Get(ref obj, propertyName); /// /// Invokes a method from on an object. @@ -131,19 +81,6 @@ namespace IPA.Utilities return dynMethod?.Invoke(obj, args); } - /// - /// Invokes a method. - /// - /// the return type - /// the object instance - /// the method name to call - /// the method's arguments - /// the return value - /// if does not exist on the runtime type of - /// - public static T InvokeMethod(this object obj, string methodName, params object[] methodArgs) - => (T)InvokeMethod(obj, methodName, methodArgs); - /// /// Invokes a method from on an object. ///