Browse Source

Added Accessor for methods

4.0.0-beta
Anairkoen Schno 4 years ago
parent
commit
098bf1888a
1 changed files with 77 additions and 6 deletions
  1. +77
    -6
      IPA.Loader/Utilities/Accessor.cs

+ 77
- 6
IPA.Loader/Utilities/Accessor.cs View File

@ -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
/// </summary>
/// <param name="name">the name of the property</param>
/// <returns>a <see cref="Getter"/> that can access that property</returns>
/// <exception cref="MissingMemberException">when the property does not exist</exception>
/// <exception cref="MissingMemberException">if the property does not exist</exception>
public static Getter GetGetter(string name) => GetAccessors(name).get;
/// <summary>
/// Gets a <see cref="Setter"/> for the property identified by <paramref name="name"/>.
/// </summary>
/// <param name="name">the name of the property</param>
/// <returns>a <see cref="Setter"/> that can access that property</returns>
/// <exception cref="MissingMemberException">when the property does not exist</exception>
/// <exception cref="MissingMemberException">if the property does not exist</exception>
public static Setter GetSetter(string name) => GetAccessors(name).set;
/// <summary>
@ -224,7 +229,7 @@ namespace IPA.Utilities
/// <param name="obj">the instance to access</param>
/// <param name="name">the name of the property</param>
/// <returns>the value of the property</returns>
/// <exception cref="MissingMemberException">when the property does not exist</exception>
/// <exception cref="MissingMemberException">if the property does not exist</exception>
/// <seealso cref="Get(T, string)"/>
/// <seealso cref="GetGetter(string)"/>
public static U Get(ref T obj, string name) => GetGetter(name)(ref obj);
@ -234,7 +239,7 @@ namespace IPA.Utilities
/// <param name="obj">the instance to access</param>
/// <param name="name">the name of the property</param>
/// <returns>the value of the property</returns>
/// <exception cref="MissingMemberException">when the property does not exist</exception>
/// <exception cref="MissingMemberException">if the property does not exist</exception>
/// <seealso cref="Get(ref T, string)"/>
/// <seealso cref="GetGetter(string)"/>
public static U Get(T obj, string name) => GetGetter(name)(ref obj);
@ -247,7 +252,7 @@ namespace IPA.Utilities
/// <param name="obj">the instance to access</param>
/// <param name="name">the name of the property</param>
/// <param name="val">the new value of the property</param>
/// <exception cref="MissingMemberException">when the property does not exist</exception>
/// <exception cref="MissingMemberException">if the property does not exist</exception>
/// <seealso cref="Set(T, string, U)"/>
/// <seealso cref="GetSetter(string)"/>
public static void Set(ref T obj, string name, U val) => GetSetter(name)(ref obj, val);
@ -260,9 +265,75 @@ namespace IPA.Utilities
/// <param name="obj">the instance to access</param>
/// <param name="name">the name of the property</param>
/// <param name="val">the new value of the property</param>
/// <exception cref="MissingMemberException">when the property does not exist</exception>
/// <exception cref="MissingMemberException">if the property does not exist</exception>
/// <seealso cref="Set(ref T, string, U)"/>
/// <seealso cref="GetSetter(string)"/>
public static void Set(T obj, string name, U val) => GetSetter(name)(ref obj, val);
}
internal class AccessorDelegateInfo<TDelegate> 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();
}
/// <summary>
/// A type containing utilities for calling non-public methods on an object.
/// </summary>
/// <typeparam name="T">the type to find the methods on</typeparam>
/// <typeparam name="TDelegate">the delegate type to create, and to use as a signature to search for</typeparam>
public static class MethodAccessor<T, TDelegate> where TDelegate : Delegate
{
private static readonly Dictionary<string, TDelegate> methods = new Dictionary<string, TDelegate>();
static MethodAccessor()
{
// ensure that first argument of delegate type is valid
var firstArg = AccessorDelegateInfo<TDelegate>.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<TDelegate>.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<ParameterModifier>());
if (method == null)
throw new MissingMethodException(typeof(T).FullName, name);
var retType = AccessorDelegateInfo<TDelegate>.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<TDelegate>.Type, method, true);
}
/// <summary>
/// Gets a delegate to the named method with the signature specified by <typeparamref name="TDelegate"/>.
/// </summary>
/// <param name="name">the name of the method to get</param>
/// <returns>a delegate that can call the specified method</returns>
/// <exception cref="MissingMethodException">if <paramref name="name"/> does not represent the name of a method with the given signature</exception>
/// <exception cref="ArgumentException">if the method found returns a type incompatable with the return type of <typeparamref name="TDelegate"/></exception>
public static TDelegate GetDelegate(string name)
{
if (!methods.TryGetValue(name, out var del))
methods.Add(name, del = MakeDelegate(name));
return del;
}
}
}

Loading…
Cancel
Save