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); 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(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(true); var setM = prop.GetSetMethod(true); 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); } }