using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
#if NET3
using Net3_Proxy;
using Array = Net3_Proxy.Array;
#endif
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);
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
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);
if (!typeof(T).IsValueType)
il.Emit(OpCodes.Ldind_Ref);
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(ref T obj);
///
/// A setter for a property.
///
/// the object it is a member of
/// the new property value
public delegate void Setter(ref 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);
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);
Getter getter = null;
Setter setter = null;
if (typeof(T).IsValueType)
{
if (getM != null)
getter = (Getter)Delegate.CreateDelegate(typeof(Getter), getM);
if (setM != null)
setter = (Setter)Delegate.CreateDelegate(typeof(Setter), setM);
}
else
{
if (getM != null)
{
var dyn = new DynamicMethod($"<>_get__{propName}", typeof(U), new[] { typeof(T).MakeByRefType() }, typeof(PropertyAccessor), true);
var il = dyn.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldind_Ref);
il.Emit(OpCodes.Tailcall);
il.Emit(getM.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, getM);
il.Emit(OpCodes.Ret);
getter = (Getter)dyn.CreateDelegate(typeof(Getter));
}
if (setM != null)
{
var dyn = new DynamicMethod($"<>_set__{propName}", typeof(void), new[] { typeof(T).MakeByRefType(), typeof(U) }, typeof(PropertyAccessor), true);
var il = dyn.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldind_Ref);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Tailcall);
il.Emit(setM.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, setM);
il.Emit(OpCodes.Ret);
setter = (Setter)dyn.CreateDelegate(typeof(Setter));
}
}
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
/// 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
/// if 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
/// if the property does not exist
///
///
public static U Get(ref T obj, string name) => GetGetter(name)(ref obj);
///
/// Gets the value of the property identified by on .
///
/// the instance to access
/// the name of the property
/// the value of the property
/// if the property does not exist
///
///
public static U Get(T obj, string name) => GetGetter(name)(ref 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
/// if the property does not exist
///
///
public static void Set(ref T obj, string name, U val) => GetSetter(name)(ref 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
/// 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;
}
}
}