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.
///