Browse Source

Added fast accessors for fields and properties for ReflectionUtil

pull/46/head
Anairkoen Schno 4 years ago
parent
commit
5ea070600e
3 changed files with 265 additions and 91 deletions
  1. +1
    -0
      IPA.Loader/IPA.Loader.csproj
  2. +236
    -0
      IPA.Loader/Utilities/Accessor.cs
  3. +28
    -91
      IPA.Loader/Utilities/ReflectionUtil.cs

+ 1
- 0
IPA.Loader/IPA.Loader.csproj View File

@ -138,6 +138,7 @@
<Compile Include="JsonConverters\SemverRangeConverter.cs" />
<Compile Include="JsonConverters\SemverVersionConverter.cs" />
<Compile Include="Utilities\Async\SingleThreadTaskScheduler.cs" />
<Compile Include="Utilities\Accessor.cs" />
<Compile Include="Utilities\UnityGame.cs" />
<Compile Include="Utilities\AlmostVersion.cs" />
<Compile Include="Utilities\CriticalSection.cs" />


+ 236
- 0
IPA.Loader/Utilities/Accessor.cs View File

@ -0,0 +1,236 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
namespace IPA.Utilities
{
/// <summary>
/// A type containing utilities for accessing non-public fields of objects.
/// </summary>
/// <typeparam name="T">the type that the fields are on</typeparam>
/// <typeparam name="U">the type of the field to access</typeparam>
/// <seealso cref="PropertyAccessor{T, U}"/>
public static class FieldAccessor<T, U>
{
/// <summary>
/// A delegate for a field accessor taking a <typeparamref name="T"/> ref and returning a <typeparamref name="U"/> ref.
/// </summary>
/// <param name="obj">the object to access the field of</param>
/// <returns>a reference to the field's value</returns>
public delegate ref U Accessor(ref T obj);
// field name -> accessor
private static readonly Dictionary<string, Accessor> accessors = new Dictionary<string, Accessor>();
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<T, U>), 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));
}
/// <summary>
/// Gets an <see cref="Accessor"/> for the field named <paramref name="name"/> on <typeparamref name="T"/>.
/// </summary>
/// <param name="name">the field name</param>
/// <returns>an accessor for the field</returns>
/// <exception cref="MissingFieldException">if the field does not exist on <typeparamref name="T"/></exception>
public static Accessor GetAccessor(string name)
{
if (!accessors.TryGetValue(name, out var accessor))
accessors.Add(name, accessor = MakeAccessor(name));
return accessor;
}
/// <summary>
/// Accesses a field for an object by name.
/// </summary>
/// <param name="obj">the object to access the field of</param>
/// <param name="name">the name of the field to access</param>
/// <returns>a reference to the object at the field</returns>
/// <exception cref="MissingFieldException">if the field does not exist on <typeparamref name="T"/></exception>
/// <seealso cref="GetAccessor(string)"/>
public static ref U Access(ref T obj, string name) => ref GetAccessor(name)(ref obj);
/// <summary>
/// Gets the value of a field of an object by name.
/// </summary>
/// <remarks>
/// The only good reason to use this over <see cref="Get(T, string)"/> is when you are working with a value type,
/// as it prevents a copy.
/// </remarks>
/// <param name="obj">the object to access the field of</param>
/// <param name="name">the name of the field to access</param>
/// <returns>the value of the field</returns>
/// <exception cref="MissingFieldException">if the field does not exist on <typeparamref name="T"/></exception>
/// <seealso cref="Get(T, string)"/>
/// <seealso cref="Access(ref T, string)"/>
/// <seealso cref="GetAccessor(string)"/>
public static U Get(ref T obj, string name) => Access(ref obj, name);
/// <summary>
/// Gets the value of a field of an object by name.
/// </summary>
/// <param name="obj">the object to access the field of</param>
/// <param name="name">the name of the field to access</param>
/// <returns>the value of the field</returns>
/// <exception cref="MissingFieldException">if the field does not exist on <typeparamref name="T"/></exception>
/// <seealso cref="Get(ref T, string)"/>
/// <seealso cref="Access(ref T, string)"/>
/// <seealso cref="GetAccessor(string)"/>
public static U Get(T obj, string name) => Get(ref obj, name);
/// <summary>
/// Sets the value of a field for an object by name.
/// </summary>
/// <remarks>
/// This overload must be used for value types.
/// </remarks>
/// <param name="obj">the object to set the field of</param>
/// <param name="name">the name of the field</param>
/// <param name="value">the value to set it to</param>
/// <exception cref="MissingFieldException">if the field does not exist on <typeparamref name="T"/></exception>
/// <seealso cref="Set(T, string, U)"/>
/// <seealso cref="Access(ref T, string)"/>
/// <seealso cref="GetAccessor(string)"/>
public static void Set(ref T obj, string name, U value) => Access(ref obj, name) = value;
/// <summary>
/// Sets the value of a field for an object by name.
/// </summary>
/// <remarks>
/// This overload cannot be safely used for value types. Use <see cref="Set(ref T, string, U)"/> instead.
/// </remarks>
/// <param name="obj">the object to set the field of</param>
/// <param name="name">the name of the field</param>
/// <param name="value">the value to set it to</param>
/// <exception cref="MissingFieldException">if the field does not exist on <typeparamref name="T"/></exception>
/// <seealso cref="Set(ref T, string, U)"/>
/// <seealso cref="Access(ref T, string)"/>
/// <seealso cref="GetAccessor(string)"/>
public static void Set(T obj, string name, U value) => Set(ref obj, name, value);
}
/// <summary>
/// A type containing utilities for accessing non-public properties of an object.
/// </summary>
/// <typeparam name="T">the type that the properties are on</typeparam>
/// <typeparam name="U">the type of the property to access</typeparam>
public static class PropertyAccessor<T, U>
{
/// <summary>
/// A getter for a property.
/// </summary>
/// <param name="obj">the object it is a member of</param>
/// <returns>the value of the property</returns>
public delegate U Getter(T obj);
/// <summary>
/// A setter for a property.
/// </summary>
/// <param name="obj">the object it is a member of</param>
/// <param name="val">the new property value</param>
public delegate void Setter(T obj, U val);
private static readonly Dictionary<string, (Getter get, Setter set)> props = new Dictionary<string, (Getter get, Setter set)>();
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;
}
/// <summary>
/// Gets a <see cref="Getter"/> for the property identified by <paramref name="name"/>.
/// </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>
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>
public static Setter GetSetter(string name) => GetAccessors(name).set;
/// <summary>
/// Gets the value of the property identified by <paramref name="name"/> on <paramref name="obj"/>.
/// </summary>
/// <remarks>
/// The only reason to use this over <see cref="Get(T, string)"/> is if you are using a value type because
/// it avoids a copy.
/// </remarks>
/// <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>
/// <seealso cref="Get(T, string)"/>
/// <seealso cref="GetGetter(string)"/>
public static U Get(ref T obj, string name) => GetGetter(name)(obj);
/// <summary>
/// Gets the value of the property identified by <paramref name="name"/> on <paramref name="obj"/>.
/// </summary>
/// <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>
/// <seealso cref="Get(ref T, string)"/>
/// <seealso cref="GetGetter(string)"/>
public static U Get(T obj, string name) => GetGetter(name)(obj);
/// <summary>
/// Sets the value of the property identified by <paramref name="name"/> on <paramref name="obj"/>.
/// </summary>
/// <remarks>
/// This overload must be used for value types.
/// </remarks>
/// <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>
/// <seealso cref="Set(T, string, U)"/>
/// <seealso cref="GetSetter(string)"/>
public static void Set(ref T obj, string name, U val) => GetSetter(name)(obj, val);
/// <summary>
/// Sets the value of the property identified by <paramref name="name"/> on <paramref name="obj"/>.
/// </summary>
/// <remarks>
/// This overload cannot be safely used for value types. Use <see cref="Set(ref T, string, U)"/> instead.
/// </remarks>
/// <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>
/// <seealso cref="Set(ref T, string, U)"/>
/// <seealso cref="GetSetter(string)"/>
public static void Set(T obj, string name, U val) => GetSetter(name)(obj, val);
}
}

+ 28
- 91
IPA.Loader/Utilities/ReflectionUtil.cs View File

@ -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
/// <summary>
/// A utility class providing reflection helper methods.
/// </summary>
public static class ReflectionUtil
public static partial class ReflectionUtil
{
/// <summary>
/// Sets a field on the target object.
/// </summary>
/// <param name="obj">the object instance</param>
/// <param name="fieldName">the field to set</param>
/// <param name="value">the value to set it to</param>
/// <exception cref="ArgumentException">if <paramref name="fieldName"/> does not exist on the runtime type of <paramref name="obj"/></exception>
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);
/// <summary>
/// Sets a field on the target object, as gotten from <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">the type to get the field from</typeparam>
/// <typeparam name="U">the type of the field to set</typeparam>
/// <param name="obj">the object instance</param>
/// <param name="fieldName">the field to set</param>
/// <param name="value">the value to set it to</param>
/// <exception cref="ArgumentException">if <paramref name="fieldName"/> does not exist on <typeparamref name="T"/></exception>
public static void SetField<T>(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);
}
/// <summary>
/// Gets the value of a field.
/// </summary>
/// <typeparam name="T">the type of the field (result casted)</typeparam>
/// <param name="obj">the object instance to pull from</param>
/// <param name="fieldName">the name of the field to read</param>
/// <returns>the value of the field</returns>
/// <exception cref="ArgumentException">if <paramref name="fieldName"/> does not exist on the runtime type of <paramref name="obj"/></exception>
public static T GetField<T>(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;
}
/// <exception cref="MissingFieldException">if <paramref name="fieldName"/> does not exist on <typeparamref name="T"/></exception>
/// <seealso cref="FieldAccessor{T, U}.Set(ref T, string, U)"/>
public static void SetField<T, U>(this T obj, string fieldName, U value)
=> FieldAccessor<T, U>.Set(ref obj, fieldName, value);
/// <summary>
/// Gets the value of a field.
@ -62,58 +34,36 @@ namespace IPA.Utilities
/// <param name="obj">the object instance to pull from</param>
/// <param name="fieldName">the name of the field to read</param>
/// <returns>the value of the field</returns>
/// <exception cref="ArgumentException">if <paramref name="fieldName"/> does not exist on <typeparamref name="U"/></exception>
/// <exception cref="MissingFieldException">if <paramref name="fieldName"/> does not exist on <typeparamref name="U"/></exception>
/// <seealso cref="FieldAccessor{T, U}.Get(ref T, string)"/>
public static T GetField<T, U>(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;
}
/// <summary>
/// Sets a property on the target object.
/// </summary>
/// <param name="obj">the target object instance</param>
/// <param name="propertyName">the name of the property</param>
/// <param name="value">the value to set it to</param>
/// <exception cref="ArgumentException">if <paramref name="propertyName"/> does not exist on the runtime type of <paramref name="obj"/></exception>
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<U, T>.Get(ref obj, fieldName);
/// <summary>
/// Sets a property on the target object, as gotten from <typeparamref name="T"/>
/// Sets a property on the target object, as gotten from <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">the type to get the property from</typeparam>
/// <typeparam name="U">the type of the property to set</typeparam>
/// <param name="obj">the object instance</param>
/// <param name="propertyName">the property to set</param>
/// <param name="value">the value to set it to</param>
/// <exception cref="ArgumentException">if <paramref name="propertyName"/> does not exist on <typeparamref name="T"/></exception>
public static void SetProperty<T>(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);
}
/// <exception cref="MissingMemberException">if <paramref name="propertyName"/> does not exist on <typeparamref name="T"/></exception>
/// <seealso cref="PropertyAccessor{T, U}.Set(ref T, string, U)"/>
public static void SetProperty<T, U>(this T obj, string propertyName, U value)
=> PropertyAccessor<T, U>.Set(ref obj, propertyName, value);
/// <summary>
/// Invokes a method on an object.
/// Gets a property on the target object, as gotten from <typeparamref name="T"/>.
/// </summary>
/// <param name="obj">the object to call from</param>
/// <param name="methodName">the method name</param>
/// <param name="methodArgs">the method arguments</param>
/// <returns>the return value</returns>
/// <exception cref="ArgumentException">if <paramref name="methodName"/> does not exist on the runtime type of <paramref name="obj"/></exception>
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);
}
/// <typeparam name="T">the type to get the property from</typeparam>
/// <typeparam name="U">the type of the property to get</typeparam>
/// <param name="obj">the object instance</param>
/// <param name="propertyName">the property to get</param>
/// <returns>the value of the property</returns>
/// <exception cref="MissingMemberException">if <paramref name="propertyName"/> does not exist on <typeparamref name="T"/></exception>
/// <seealso cref="PropertyAccessor{T, U}.Get(ref T, string)"/>
public static U GetProperty<T, U>(this T obj, string propertyName)
=> PropertyAccessor<T, U>.Get(ref obj, propertyName);
/// <summary>
/// Invokes a method from <typeparamref name="T"/> on an object.
@ -131,19 +81,6 @@ namespace IPA.Utilities
return dynMethod?.Invoke(obj, args);
}
/// <summary>
/// Invokes a method.
/// </summary>
/// <typeparam name="T">the return type</typeparam>
/// <param name="obj">the object instance</param>
/// <param name="methodName">the method name to call</param>
/// <param name="methodArgs">the method's arguments</param>
/// <returns>the return value</returns>
/// <exception cref="ArgumentException">if <paramref name="methodName"/> does not exist on the runtime type of <paramref name="obj"/></exception>
/// <seealso cref="InvokeMethod(object, string, object[])"/>
public static T InvokeMethod<T>(this object obj, string methodName, params object[] methodArgs)
=> (T)InvokeMethod(obj, methodName, methodArgs);
/// <summary>
/// Invokes a method from <typeparamref name="U"/> on an object.
/// </summary>


Loading…
Cancel
Save