diff --git a/IPA.Loader/Utilities/Accessor.cs b/IPA.Loader/Utilities/Accessor.cs
index 6e79bf36..a612e097 100644
--- a/IPA.Loader/Utilities/Accessor.cs
+++ b/IPA.Loader/Utilities/Accessor.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
@@ -28,6 +29,8 @@ namespace IPA.Utilities
var field = typeof(T).GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
if (field == null)
throw new MissingFieldException(typeof(T).Name, fieldName);
+ if (field.FieldType != typeof(U))
+ throw new ArgumentException($"Field '{fieldName}' not of type {typeof(U)}");
var dynMethodName = $"<>_accessor__{fieldName}";
// unfortunately DynamicMethod doesn't like having a ByRef return type, so reflection it
@@ -149,6 +152,8 @@ namespace IPA.Utilities
var prop = typeof(T).GetProperty(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
if (prop == null)
throw new MissingMemberException(typeof(T).Name, propName);
+ if (prop.PropertyType != typeof(U))
+ throw new ArgumentException($"Property '{propName}' on {typeof(T)} is not of type {typeof(U)}");
var getM = prop.GetGetMethod(true);
var setM = prop.GetSetMethod(true);
@@ -204,14 +209,14 @@ namespace IPA.Utilities
///
/// the name of the property
/// a that can access that property
- /// when the property does not exist
+ /// if 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
+ /// if the property does not exist
public static Setter GetSetter(string name) => GetAccessors(name).set;
///
@@ -224,7 +229,7 @@ namespace IPA.Utilities
/// the instance to access
/// the name of the property
/// the value of the property
- /// when the property does not exist
+ /// if the property does not exist
///
///
public static U Get(ref T obj, string name) => GetGetter(name)(ref obj);
@@ -234,7 +239,7 @@ namespace IPA.Utilities
/// the instance to access
/// the name of the property
/// the value of the property
- /// when the property does not exist
+ /// if the property does not exist
///
///
public static U Get(T obj, string name) => GetGetter(name)(ref obj);
@@ -247,7 +252,7 @@ namespace IPA.Utilities
/// the instance to access
/// the name of the property
/// the new value of the property
- /// when the property does not exist
+ /// if the property does not exist
///
///
public static void Set(ref T obj, string name, U val) => GetSetter(name)(ref obj, val);
@@ -260,9 +265,75 @@ namespace IPA.Utilities
/// the instance to access
/// the name of the property
/// the new value of the property
- /// when the property does not exist
+ /// if the property does not exist
///
///
public static void Set(T obj, string name, U val) => GetSetter(name)(ref obj, val);
}
+
+ internal class AccessorDelegateInfo where TDelegate : Delegate
+ {
+ public static readonly Type Type = typeof(TDelegate);
+ public static readonly MethodInfo Invoke = Type.GetMethod("Invoke");
+ public static readonly ParameterInfo[] Parameters = Invoke.GetParameters();
+ }
+
+ ///
+ /// A type containing utilities for calling non-public methods on an object.
+ ///
+ /// the type to find the methods on
+ /// the delegate type to create, and to use as a signature to search for
+ public static class MethodAccessor where TDelegate : Delegate
+ {
+ private static readonly Dictionary methods = new Dictionary();
+
+ static MethodAccessor()
+ {
+ // ensure that first argument of delegate type is valid
+ var firstArg = AccessorDelegateInfo.Parameters.First();
+ var firstType = firstArg.ParameterType;
+
+ if (typeof(T).IsValueType)
+ {
+ if (!firstType.IsByRef)
+ throw new InvalidOperationException("First parameter of a method accessor to a value type is not byref");
+ else
+ firstType = firstType.GetElementType(); // get the non-byref type to check compatability
+ }
+
+ if (!typeof(T).IsAssignableFrom(firstType))
+ throw new InvalidOperationException("First parameter of a method accessor is not assignable to the method owning type");
+ }
+
+ private static TDelegate MakeDelegate(string name)
+ {
+ var delParams = AccessorDelegateInfo.Parameters;
+ var method = typeof(T).GetMethod(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly,
+ null, delParams.Skip(1).Select(p => p.ParameterType).ToArray(), Array.Empty());
+
+ if (method == null)
+ throw new MissingMethodException(typeof(T).FullName, name);
+
+ var retType = AccessorDelegateInfo.Invoke.ReturnType;
+ if (!retType.IsAssignableFrom(method.ReturnType))
+ throw new ArgumentException($"The method found returns a type incompatable with the return type of {typeof(TDelegate)}");
+
+ return (TDelegate)Delegate.CreateDelegate(AccessorDelegateInfo.Type, method, true);
+ }
+
+ ///
+ /// Gets a delegate to the named method with the signature specified by .
+ ///
+ /// the name of the method to get
+ /// a delegate that can call the specified method
+ /// if does not represent the name of a method with the given signature
+ /// if the method found returns a type incompatable with the return type of
+ public static TDelegate GetDelegate(string name)
+ {
+ if (!methods.TryGetValue(name, out var del))
+ methods.Add(name, del = MakeDelegate(name));
+ return del;
+ }
+ }
+
}