You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

236 lines
12 KiB

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Reflection;
  4. using System.Reflection.Emit;
  5. namespace IPA.Utilities
  6. {
  7. /// <summary>
  8. /// A type containing utilities for accessing non-public fields of objects.
  9. /// </summary>
  10. /// <typeparam name="T">the type that the fields are on</typeparam>
  11. /// <typeparam name="U">the type of the field to access</typeparam>
  12. /// <seealso cref="PropertyAccessor{T, U}"/>
  13. public static class FieldAccessor<T, U>
  14. {
  15. /// <summary>
  16. /// A delegate for a field accessor taking a <typeparamref name="T"/> ref and returning a <typeparamref name="U"/> ref.
  17. /// </summary>
  18. /// <param name="obj">the object to access the field of</param>
  19. /// <returns>a reference to the field's value</returns>
  20. public delegate ref U Accessor(ref T obj);
  21. // field name -> accessor
  22. private static readonly Dictionary<string, Accessor> accessors = new Dictionary<string, Accessor>();
  23. private static Accessor MakeAccessor(string fieldName)
  24. {
  25. var field = typeof(T).GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
  26. if (field == null)
  27. throw new MissingFieldException(typeof(T).Name, fieldName);
  28. var dynMethodName = $"<>_accessor__{fieldName}";
  29. // unfortunately DynamicMethod doesn't like having a ByRef return type, so reflection it
  30. var dyn = new DynamicMethod(dynMethodName, typeof(U), new[] { typeof(T).MakeByRefType() }, typeof(FieldAccessor<T, U>), true);
  31. ReflectionUtil.DynamicMethodReturnType.SetValue(dyn, typeof(U).MakeByRefType());
  32. var il = dyn.GetILGenerator();
  33. il.Emit(OpCodes.Ldarg_0);
  34. il.Emit(OpCodes.Ldflda, field);
  35. il.Emit(OpCodes.Ret);
  36. return (Accessor)dyn.CreateDelegate(typeof(Accessor));
  37. }
  38. /// <summary>
  39. /// Gets an <see cref="Accessor"/> for the field named <paramref name="name"/> on <typeparamref name="T"/>.
  40. /// </summary>
  41. /// <param name="name">the field name</param>
  42. /// <returns>an accessor for the field</returns>
  43. /// <exception cref="MissingFieldException">if the field does not exist on <typeparamref name="T"/></exception>
  44. public static Accessor GetAccessor(string name)
  45. {
  46. if (!accessors.TryGetValue(name, out var accessor))
  47. accessors.Add(name, accessor = MakeAccessor(name));
  48. return accessor;
  49. }
  50. /// <summary>
  51. /// Accesses a field for an object by name.
  52. /// </summary>
  53. /// <param name="obj">the object to access the field of</param>
  54. /// <param name="name">the name of the field to access</param>
  55. /// <returns>a reference to the object at the field</returns>
  56. /// <exception cref="MissingFieldException">if the field does not exist on <typeparamref name="T"/></exception>
  57. /// <seealso cref="GetAccessor(string)"/>
  58. public static ref U Access(ref T obj, string name) => ref GetAccessor(name)(ref obj);
  59. /// <summary>
  60. /// Gets the value of a field of an object by name.
  61. /// </summary>
  62. /// <remarks>
  63. /// The only good reason to use this over <see cref="Get(T, string)"/> is when you are working with a value type,
  64. /// as it prevents a copy.
  65. /// </remarks>
  66. /// <param name="obj">the object to access the field of</param>
  67. /// <param name="name">the name of the field to access</param>
  68. /// <returns>the value of the field</returns>
  69. /// <exception cref="MissingFieldException">if the field does not exist on <typeparamref name="T"/></exception>
  70. /// <seealso cref="Get(T, string)"/>
  71. /// <seealso cref="Access(ref T, string)"/>
  72. /// <seealso cref="GetAccessor(string)"/>
  73. public static U Get(ref T obj, string name) => Access(ref obj, name);
  74. /// <summary>
  75. /// Gets the value of a field of an object by name.
  76. /// </summary>
  77. /// <param name="obj">the object to access the field of</param>
  78. /// <param name="name">the name of the field to access</param>
  79. /// <returns>the value of the field</returns>
  80. /// <exception cref="MissingFieldException">if the field does not exist on <typeparamref name="T"/></exception>
  81. /// <seealso cref="Get(ref T, string)"/>
  82. /// <seealso cref="Access(ref T, string)"/>
  83. /// <seealso cref="GetAccessor(string)"/>
  84. public static U Get(T obj, string name) => Get(ref obj, name);
  85. /// <summary>
  86. /// Sets the value of a field for an object by name.
  87. /// </summary>
  88. /// <remarks>
  89. /// This overload must be used for value types.
  90. /// </remarks>
  91. /// <param name="obj">the object to set the field of</param>
  92. /// <param name="name">the name of the field</param>
  93. /// <param name="value">the value to set it to</param>
  94. /// <exception cref="MissingFieldException">if the field does not exist on <typeparamref name="T"/></exception>
  95. /// <seealso cref="Set(T, string, U)"/>
  96. /// <seealso cref="Access(ref T, string)"/>
  97. /// <seealso cref="GetAccessor(string)"/>
  98. public static void Set(ref T obj, string name, U value) => Access(ref obj, name) = value;
  99. /// <summary>
  100. /// Sets the value of a field for an object by name.
  101. /// </summary>
  102. /// <remarks>
  103. /// This overload cannot be safely used for value types. Use <see cref="Set(ref T, string, U)"/> instead.
  104. /// </remarks>
  105. /// <param name="obj">the object to set the field of</param>
  106. /// <param name="name">the name of the field</param>
  107. /// <param name="value">the value to set it to</param>
  108. /// <exception cref="MissingFieldException">if the field does not exist on <typeparamref name="T"/></exception>
  109. /// <seealso cref="Set(ref T, string, U)"/>
  110. /// <seealso cref="Access(ref T, string)"/>
  111. /// <seealso cref="GetAccessor(string)"/>
  112. public static void Set(T obj, string name, U value) => Set(ref obj, name, value);
  113. }
  114. /// <summary>
  115. /// A type containing utilities for accessing non-public properties of an object.
  116. /// </summary>
  117. /// <typeparam name="T">the type that the properties are on</typeparam>
  118. /// <typeparam name="U">the type of the property to access</typeparam>
  119. public static class PropertyAccessor<T, U>
  120. {
  121. /// <summary>
  122. /// A getter for a property.
  123. /// </summary>
  124. /// <param name="obj">the object it is a member of</param>
  125. /// <returns>the value of the property</returns>
  126. public delegate U Getter(T obj);
  127. /// <summary>
  128. /// A setter for a property.
  129. /// </summary>
  130. /// <param name="obj">the object it is a member of</param>
  131. /// <param name="val">the new property value</param>
  132. public delegate void Setter(T obj, U val);
  133. private static readonly Dictionary<string, (Getter get, Setter set)> props = new Dictionary<string, (Getter get, Setter set)>();
  134. private static (Getter, Setter) MakeAccessors(string propName)
  135. {
  136. var prop = typeof(T).GetProperty(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
  137. if (prop == null)
  138. throw new MissingMemberException(typeof(T).Name, propName);
  139. var getM = prop.GetGetMethod();
  140. var setM = prop.GetSetMethod();
  141. Getter getter = null;
  142. Setter setter = null;
  143. if (getM != null)
  144. getter = (Getter)Delegate.CreateDelegate(typeof(Getter), getM);
  145. if (setM != null)
  146. setter = (Setter)Delegate.CreateDelegate(typeof(Setter), setM);
  147. return (getter, setter);
  148. }
  149. private static (Getter get, Setter set) GetAccessors(string propName)
  150. {
  151. if (!props.TryGetValue(propName, out var access))
  152. props.Add(propName, access = MakeAccessors(propName));
  153. return access;
  154. }
  155. /// <summary>
  156. /// Gets a <see cref="Getter"/> for the property identified by <paramref name="name"/>.
  157. /// </summary>
  158. /// <param name="name">the name of the property</param>
  159. /// <returns>a <see cref="Getter"/> that can access that property</returns>
  160. /// <exception cref="MissingMemberException">when the property does not exist</exception>
  161. public static Getter GetGetter(string name) => GetAccessors(name).get;
  162. /// <summary>
  163. /// Gets a <see cref="Setter"/> for the property identified by <paramref name="name"/>.
  164. /// </summary>
  165. /// <param name="name">the name of the property</param>
  166. /// <returns>a <see cref="Setter"/> that can access that property</returns>
  167. /// <exception cref="MissingMemberException">when the property does not exist</exception>
  168. public static Setter GetSetter(string name) => GetAccessors(name).set;
  169. /// <summary>
  170. /// Gets the value of the property identified by <paramref name="name"/> on <paramref name="obj"/>.
  171. /// </summary>
  172. /// <remarks>
  173. /// The only reason to use this over <see cref="Get(T, string)"/> is if you are using a value type because
  174. /// it avoids a copy.
  175. /// </remarks>
  176. /// <param name="obj">the instance to access</param>
  177. /// <param name="name">the name of the property</param>
  178. /// <returns>the value of the property</returns>
  179. /// <exception cref="MissingMemberException">when the property does not exist</exception>
  180. /// <seealso cref="Get(T, string)"/>
  181. /// <seealso cref="GetGetter(string)"/>
  182. public static U Get(ref T obj, string name) => GetGetter(name)(obj);
  183. /// <summary>
  184. /// Gets the value of the property identified by <paramref name="name"/> on <paramref name="obj"/>.
  185. /// </summary>
  186. /// <param name="obj">the instance to access</param>
  187. /// <param name="name">the name of the property</param>
  188. /// <returns>the value of the property</returns>
  189. /// <exception cref="MissingMemberException">when the property does not exist</exception>
  190. /// <seealso cref="Get(ref T, string)"/>
  191. /// <seealso cref="GetGetter(string)"/>
  192. public static U Get(T obj, string name) => GetGetter(name)(obj);
  193. /// <summary>
  194. /// Sets the value of the property identified by <paramref name="name"/> on <paramref name="obj"/>.
  195. /// </summary>
  196. /// <remarks>
  197. /// This overload must be used for value types.
  198. /// </remarks>
  199. /// <param name="obj">the instance to access</param>
  200. /// <param name="name">the name of the property</param>
  201. /// <param name="val">the new value of the property</param>
  202. /// <exception cref="MissingMemberException">when the property does not exist</exception>
  203. /// <seealso cref="Set(T, string, U)"/>
  204. /// <seealso cref="GetSetter(string)"/>
  205. public static void Set(ref T obj, string name, U val) => GetSetter(name)(obj, val);
  206. /// <summary>
  207. /// Sets the value of the property identified by <paramref name="name"/> on <paramref name="obj"/>.
  208. /// </summary>
  209. /// <remarks>
  210. /// This overload cannot be safely used for value types. Use <see cref="Set(ref T, string, U)"/> instead.
  211. /// </remarks>
  212. /// <param name="obj">the instance to access</param>
  213. /// <param name="name">the name of the property</param>
  214. /// <param name="val">the new value of the property</param>
  215. /// <exception cref="MissingMemberException">when the property does not exist</exception>
  216. /// <seealso cref="Set(ref T, string, U)"/>
  217. /// <seealso cref="GetSetter(string)"/>
  218. public static void Set(T obj, string name, U val) => GetSetter(name)(obj, val);
  219. }
  220. }