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.

268 lines
14 KiB

4 years ago
4 years ago
  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. if (!typeof(T).IsValueType)
  35. il.Emit(OpCodes.Ldind_Ref);
  36. il.Emit(OpCodes.Ldflda, field);
  37. il.Emit(OpCodes.Ret);
  38. return (Accessor)dyn.CreateDelegate(typeof(Accessor));
  39. }
  40. /// <summary>
  41. /// Gets an <see cref="Accessor"/> for the field named <paramref name="name"/> on <typeparamref name="T"/>.
  42. /// </summary>
  43. /// <param name="name">the field name</param>
  44. /// <returns>an accessor for the field</returns>
  45. /// <exception cref="MissingFieldException">if the field does not exist on <typeparamref name="T"/></exception>
  46. public static Accessor GetAccessor(string name)
  47. {
  48. if (!accessors.TryGetValue(name, out var accessor))
  49. accessors.Add(name, accessor = MakeAccessor(name));
  50. return accessor;
  51. }
  52. /// <summary>
  53. /// Accesses a field for an object by name.
  54. /// </summary>
  55. /// <param name="obj">the object to access the field of</param>
  56. /// <param name="name">the name of the field to access</param>
  57. /// <returns>a reference to the object at the field</returns>
  58. /// <exception cref="MissingFieldException">if the field does not exist on <typeparamref name="T"/></exception>
  59. /// <seealso cref="GetAccessor(string)"/>
  60. public static ref U Access(ref T obj, string name) => ref GetAccessor(name)(ref obj);
  61. /// <summary>
  62. /// Gets the value of a field of an object by name.
  63. /// </summary>
  64. /// <remarks>
  65. /// The only good reason to use this over <see cref="Get(T, string)"/> is when you are working with a value type,
  66. /// as it prevents a copy.
  67. /// </remarks>
  68. /// <param name="obj">the object to access the field of</param>
  69. /// <param name="name">the name of the field to access</param>
  70. /// <returns>the value of the field</returns>
  71. /// <exception cref="MissingFieldException">if the field does not exist on <typeparamref name="T"/></exception>
  72. /// <seealso cref="Get(T, string)"/>
  73. /// <seealso cref="Access(ref T, string)"/>
  74. /// <seealso cref="GetAccessor(string)"/>
  75. public static U Get(ref T obj, string name) => Access(ref obj, name);
  76. /// <summary>
  77. /// Gets the value of a field of an object by name.
  78. /// </summary>
  79. /// <param name="obj">the object to access the field of</param>
  80. /// <param name="name">the name of the field to access</param>
  81. /// <returns>the value of the field</returns>
  82. /// <exception cref="MissingFieldException">if the field does not exist on <typeparamref name="T"/></exception>
  83. /// <seealso cref="Get(ref T, string)"/>
  84. /// <seealso cref="Access(ref T, string)"/>
  85. /// <seealso cref="GetAccessor(string)"/>
  86. public static U Get(T obj, string name) => Get(ref obj, name);
  87. /// <summary>
  88. /// Sets the value of a field for an object by name.
  89. /// </summary>
  90. /// <remarks>
  91. /// This overload must be used for value types.
  92. /// </remarks>
  93. /// <param name="obj">the object to set the field of</param>
  94. /// <param name="name">the name of the field</param>
  95. /// <param name="value">the value to set it to</param>
  96. /// <exception cref="MissingFieldException">if the field does not exist on <typeparamref name="T"/></exception>
  97. /// <seealso cref="Set(T, string, U)"/>
  98. /// <seealso cref="Access(ref T, string)"/>
  99. /// <seealso cref="GetAccessor(string)"/>
  100. public static void Set(ref T obj, string name, U value) => Access(ref obj, name) = value;
  101. /// <summary>
  102. /// Sets the value of a field for an object by name.
  103. /// </summary>
  104. /// <remarks>
  105. /// This overload cannot be safely used for value types. Use <see cref="Set(ref T, string, U)"/> instead.
  106. /// </remarks>
  107. /// <param name="obj">the object to set the field of</param>
  108. /// <param name="name">the name of the field</param>
  109. /// <param name="value">the value to set it to</param>
  110. /// <exception cref="MissingFieldException">if the field does not exist on <typeparamref name="T"/></exception>
  111. /// <seealso cref="Set(ref T, string, U)"/>
  112. /// <seealso cref="Access(ref T, string)"/>
  113. /// <seealso cref="GetAccessor(string)"/>
  114. public static void Set(T obj, string name, U value) => Set(ref obj, name, value);
  115. }
  116. /// <summary>
  117. /// A type containing utilities for accessing non-public properties of an object.
  118. /// </summary>
  119. /// <typeparam name="T">the type that the properties are on</typeparam>
  120. /// <typeparam name="U">the type of the property to access</typeparam>
  121. public static class PropertyAccessor<T, U>
  122. {
  123. /// <summary>
  124. /// A getter for a property.
  125. /// </summary>
  126. /// <param name="obj">the object it is a member of</param>
  127. /// <returns>the value of the property</returns>
  128. public delegate U Getter(ref T obj);
  129. /// <summary>
  130. /// A setter for a property.
  131. /// </summary>
  132. /// <param name="obj">the object it is a member of</param>
  133. /// <param name="val">the new property value</param>
  134. public delegate void Setter(ref T obj, U val);
  135. private static readonly Dictionary<string, (Getter get, Setter set)> props = new Dictionary<string, (Getter get, Setter set)>();
  136. private static (Getter, Setter) MakeAccessors(string propName)
  137. {
  138. var prop = typeof(T).GetProperty(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
  139. if (prop == null)
  140. throw new MissingMemberException(typeof(T).Name, propName);
  141. var getM = prop.GetGetMethod(true);
  142. var setM = prop.GetSetMethod(true);
  143. Getter getter = null;
  144. Setter setter = null;
  145. if (typeof(T).IsValueType)
  146. {
  147. if (getM != null)
  148. getter = (Getter)Delegate.CreateDelegate(typeof(Getter), getM);
  149. if (setM != null)
  150. setter = (Setter)Delegate.CreateDelegate(typeof(Setter), setM);
  151. }
  152. else
  153. {
  154. if (getM != null)
  155. {
  156. var dyn = new DynamicMethod($"<>_get__{propName}", typeof(U), new[] { typeof(T).MakeByRefType() }, typeof(PropertyAccessor<T, U>), true);
  157. var il = dyn.GetILGenerator();
  158. il.Emit(OpCodes.Ldarg_0);
  159. il.Emit(OpCodes.Ldind_Ref);
  160. il.Emit(OpCodes.Tailcall);
  161. il.Emit(getM.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, getM);
  162. il.Emit(OpCodes.Ret);
  163. getter = (Getter)dyn.CreateDelegate(typeof(Getter));
  164. }
  165. if (setM != null)
  166. {
  167. var dyn = new DynamicMethod($"<>_set__{propName}", typeof(void), new[] { typeof(T).MakeByRefType(), typeof(U) }, typeof(PropertyAccessor<T, U>), true);
  168. var il = dyn.GetILGenerator();
  169. il.Emit(OpCodes.Ldarg_0);
  170. il.Emit(OpCodes.Ldind_Ref);
  171. il.Emit(OpCodes.Ldarg_1);
  172. il.Emit(OpCodes.Tailcall);
  173. il.Emit(setM.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, setM);
  174. il.Emit(OpCodes.Ret);
  175. setter = (Setter)dyn.CreateDelegate(typeof(Setter));
  176. }
  177. }
  178. return (getter, setter);
  179. }
  180. private static (Getter get, Setter set) GetAccessors(string propName)
  181. {
  182. if (!props.TryGetValue(propName, out var access))
  183. props.Add(propName, access = MakeAccessors(propName));
  184. return access;
  185. }
  186. /// <summary>
  187. /// Gets a <see cref="Getter"/> for the property identified by <paramref name="name"/>.
  188. /// </summary>
  189. /// <param name="name">the name of the property</param>
  190. /// <returns>a <see cref="Getter"/> that can access that property</returns>
  191. /// <exception cref="MissingMemberException">when the property does not exist</exception>
  192. public static Getter GetGetter(string name) => GetAccessors(name).get;
  193. /// <summary>
  194. /// Gets a <see cref="Setter"/> for the property identified by <paramref name="name"/>.
  195. /// </summary>
  196. /// <param name="name">the name of the property</param>
  197. /// <returns>a <see cref="Setter"/> that can access that property</returns>
  198. /// <exception cref="MissingMemberException">when the property does not exist</exception>
  199. public static Setter GetSetter(string name) => GetAccessors(name).set;
  200. /// <summary>
  201. /// Gets the value of the property identified by <paramref name="name"/> on <paramref name="obj"/>.
  202. /// </summary>
  203. /// <remarks>
  204. /// The only reason to use this over <see cref="Get(T, string)"/> is if you are using a value type because
  205. /// it avoids a copy.
  206. /// </remarks>
  207. /// <param name="obj">the instance to access</param>
  208. /// <param name="name">the name of the property</param>
  209. /// <returns>the value of the property</returns>
  210. /// <exception cref="MissingMemberException">when the property does not exist</exception>
  211. /// <seealso cref="Get(T, string)"/>
  212. /// <seealso cref="GetGetter(string)"/>
  213. public static U Get(ref T obj, string name) => GetGetter(name)(ref obj);
  214. /// <summary>
  215. /// Gets the value of the property identified by <paramref name="name"/> on <paramref name="obj"/>.
  216. /// </summary>
  217. /// <param name="obj">the instance to access</param>
  218. /// <param name="name">the name of the property</param>
  219. /// <returns>the value of the property</returns>
  220. /// <exception cref="MissingMemberException">when the property does not exist</exception>
  221. /// <seealso cref="Get(ref T, string)"/>
  222. /// <seealso cref="GetGetter(string)"/>
  223. public static U Get(T obj, string name) => GetGetter(name)(ref obj);
  224. /// <summary>
  225. /// Sets the value of the property identified by <paramref name="name"/> on <paramref name="obj"/>.
  226. /// </summary>
  227. /// <remarks>
  228. /// This overload must be used for value types.
  229. /// </remarks>
  230. /// <param name="obj">the instance to access</param>
  231. /// <param name="name">the name of the property</param>
  232. /// <param name="val">the new value of the property</param>
  233. /// <exception cref="MissingMemberException">when the property does not exist</exception>
  234. /// <seealso cref="Set(T, string, U)"/>
  235. /// <seealso cref="GetSetter(string)"/>
  236. public static void Set(ref T obj, string name, U val) => GetSetter(name)(ref obj, val);
  237. /// <summary>
  238. /// Sets the value of the property identified by <paramref name="name"/> on <paramref name="obj"/>.
  239. /// </summary>
  240. /// <remarks>
  241. /// This overload cannot be safely used for value types. Use <see cref="Set(ref T, string, U)"/> instead.
  242. /// </remarks>
  243. /// <param name="obj">the instance to access</param>
  244. /// <param name="name">the name of the property</param>
  245. /// <param name="val">the new value of the property</param>
  246. /// <exception cref="MissingMemberException">when the property does not exist</exception>
  247. /// <seealso cref="Set(ref T, string, U)"/>
  248. /// <seealso cref="GetSetter(string)"/>
  249. public static void Set(T obj, string name, U val) => GetSetter(name)(ref obj, val);
  250. }
  251. }