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.

219 lines
12 KiB

  1. using System;
  2. using System.Reflection;
  3. using UnityEngine;
  4. namespace IPA.Utilities
  5. {
  6. /// <summary>
  7. /// A utility class providing reflection helper methods.
  8. /// </summary>
  9. public static class ReflectionUtil
  10. {
  11. /// <summary>
  12. /// Sets a field on the target object.
  13. /// </summary>
  14. /// <param name="obj">the object instance</param>
  15. /// <param name="fieldName">the field to set</param>
  16. /// <param name="value">the value to set it to</param>
  17. /// <exception cref="ArgumentException">if <paramref name="fieldName"/> does not exist on the runtime type of <paramref name="obj"/></exception>
  18. public static void SetField(this object obj, string fieldName, object value)
  19. {
  20. var prop = obj.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
  21. if (prop == null) throw new ArgumentException($"Field {fieldName} does not exist", nameof(fieldName));
  22. prop?.SetValue(obj, value);
  23. }
  24. /// <summary>
  25. /// Sets a field on the target object, as gotten from <typeparamref name="T"/>.
  26. /// </summary>
  27. /// <typeparam name="T">the type to get the field from</typeparam>
  28. /// <param name="obj">the object instance</param>
  29. /// <param name="fieldName">the field to set</param>
  30. /// <param name="value">the value to set it to</param>
  31. /// <exception cref="ArgumentException">if <paramref name="fieldName"/> does not exist on <typeparamref name="T"/></exception>
  32. public static void SetField<T>(this T obj, string fieldName, object value)
  33. {
  34. var prop = typeof(T).GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
  35. if (prop == null) throw new ArgumentException($"Field {fieldName} does not exist", nameof(fieldName));
  36. prop?.SetValue(obj, value);
  37. }
  38. /// <summary>
  39. /// Gets the value of a field.
  40. /// </summary>
  41. /// <typeparam name="T">the type of the field (result casted)</typeparam>
  42. /// <param name="obj">the object instance to pull from</param>
  43. /// <param name="fieldName">the name of the field to read</param>
  44. /// <returns>the value of the field</returns>
  45. /// <exception cref="ArgumentException">if <paramref name="fieldName"/> does not exist on the runtime type of <paramref name="obj"/></exception>
  46. public static T GetField<T>(this object obj, string fieldName)
  47. {
  48. var prop = obj.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
  49. if (prop == null) throw new ArgumentException($"Field {fieldName} does not exist", nameof(fieldName));
  50. var value = prop?.GetValue(obj);
  51. return (T) value;
  52. }
  53. /// <summary>
  54. /// Gets the value of a field.
  55. /// </summary>
  56. /// <typeparam name="T">the type of the field (result casted)</typeparam>
  57. /// <typeparam name="U">the type to get the field from</typeparam>
  58. /// <param name="obj">the object instance to pull from</param>
  59. /// <param name="fieldName">the name of the field to read</param>
  60. /// <returns>the value of the field</returns>
  61. /// <exception cref="ArgumentException">if <paramref name="fieldName"/> does not exist on <typeparamref name="U"/></exception>
  62. public static T GetField<T, U>(this U obj, string fieldName)
  63. {
  64. var prop = typeof(U).GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
  65. if (prop == null) throw new ArgumentException($"Field {fieldName} does not exist", nameof(fieldName));
  66. var value = prop?.GetValue(obj);
  67. return (T)value;
  68. }
  69. /// <summary>
  70. /// Sets a property on the target object.
  71. /// </summary>
  72. /// <param name="obj">the target object instance</param>
  73. /// <param name="propertyName">the name of the property</param>
  74. /// <param name="value">the value to set it to</param>
  75. /// <exception cref="ArgumentException">if <paramref name="propertyName"/> does not exist on the runtime type of <paramref name="obj"/></exception>
  76. public static void SetProperty(this object obj, string propertyName, object value)
  77. {
  78. var prop = obj.GetType().GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
  79. if (prop == null) throw new ArgumentException($"Property {propertyName} does not exist", nameof(propertyName));
  80. prop?.SetValue(obj, value, null);
  81. }
  82. /// <summary>
  83. /// Sets a property on the target object, as gotten from <typeparamref name="T"/>
  84. /// </summary>
  85. /// <typeparam name="T">the type to get the property from</typeparam>
  86. /// <param name="obj">the object instance</param>
  87. /// <param name="propertyName">the property to set</param>
  88. /// <param name="value">the value to set it to</param>
  89. /// <exception cref="ArgumentException">if <paramref name="propertyName"/> does not exist on <typeparamref name="T"/></exception>
  90. public static void SetProperty<T>(this T obj, string propertyName, object value)
  91. {
  92. var prop = typeof(T).GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
  93. if (prop == null) throw new ArgumentException($"Property {propertyName} does not exist", nameof(propertyName));
  94. prop?.SetValue(obj, value, null);
  95. }
  96. /// <summary>
  97. /// Invokes a method on an object.
  98. /// </summary>
  99. /// <param name="obj">the object to call from</param>
  100. /// <param name="methodName">the method name</param>
  101. /// <param name="methodArgs">the method arguments</param>
  102. /// <returns>the return value</returns>
  103. /// <exception cref="ArgumentException">if <paramref name="methodName"/> does not exist on the runtime type of <paramref name="obj"/></exception>
  104. public static object InvokeMethod(this object obj, string methodName, params object[] methodArgs)
  105. {
  106. MethodInfo dynMethod = obj.GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
  107. if (dynMethod == null) throw new ArgumentException($"Method {methodName} does not exist", nameof(methodName));
  108. return dynMethod?.Invoke(obj, methodArgs);
  109. }
  110. /// <summary>
  111. /// Invokes a method from <typeparamref name="T"/> on an object.
  112. /// </summary>
  113. /// <typeparam name="T">the type to search for the method on</typeparam>
  114. /// <param name="obj">the object instance</param>
  115. /// <param name="methodName">the method's name</param>
  116. /// <param name="args">the method arguments</param>
  117. /// <returns>the return value</returns>
  118. /// <exception cref="ArgumentException">if <paramref name="methodName"/> does not exist on <typeparamref name="T"/></exception>
  119. public static object InvokeMethod<T>(this T obj, string methodName, params object[] args)
  120. {
  121. var dynMethod = typeof(T).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
  122. if (dynMethod == null) throw new ArgumentException($"Method {methodName} does not exist", nameof(methodName));
  123. return dynMethod?.Invoke(obj, args);
  124. }
  125. /// <summary>
  126. /// Invokes a method.
  127. /// </summary>
  128. /// <typeparam name="T">the return type</typeparam>
  129. /// <param name="obj">the object instance</param>
  130. /// <param name="methodName">the method name to call</param>
  131. /// <param name="methodArgs">the method's arguments</param>
  132. /// <returns>the return value</returns>
  133. /// <exception cref="ArgumentException">if <paramref name="methodName"/> does not exist on the runtime type of <paramref name="obj"/></exception>
  134. /// <seealso cref="InvokeMethod(object, string, object[])"/>
  135. public static T InvokeMethod<T>(this object obj, string methodName, params object[] methodArgs)
  136. => (T)InvokeMethod(obj, methodName, methodArgs);
  137. /// <summary>
  138. /// Invokes a method from <typeparamref name="U"/> on an object.
  139. /// </summary>
  140. /// <typeparam name="T">the return type</typeparam>
  141. /// <typeparam name="U">the type to search for the method on</typeparam>
  142. /// <param name="obj">the object instance</param>
  143. /// <param name="methodName">the method name to call</param>
  144. /// <param name="methodArgs">the method's arguments</param>
  145. /// <returns>the return value</returns>
  146. /// <exception cref="ArgumentException">if <paramref name="methodName"/> does not exist on <typeparamref name="U"/></exception>
  147. /// <seealso cref="InvokeMethod{T}(T, string, object[])"/>
  148. public static T InvokeMethod<T, U>(this U obj, string methodName, params object[] methodArgs)
  149. => (T)InvokeMethod(obj, methodName, methodArgs);
  150. /// <summary>
  151. /// Copies a component <paramref name="original"/> to a component of <paramref name="overridingType"/> on the destination <see cref="GameObject"/>.
  152. /// </summary>
  153. /// <param name="original">the original component</param>
  154. /// <param name="overridingType">the new component's type</param>
  155. /// <param name="destination">the destination GameObject</param>
  156. /// <param name="originalTypeOverride">overrides the source component type (for example, to a superclass)</param>
  157. /// <returns>the copied component</returns>
  158. public static Component CopyComponent(this Component original, Type overridingType, GameObject destination, Type originalTypeOverride = null)
  159. {
  160. var copy = destination.AddComponent(overridingType);
  161. var originalType = originalTypeOverride ?? original.GetType();
  162. Type type = originalType;
  163. while (type != typeof(MonoBehaviour))
  164. {
  165. CopyForType(type, original, copy);
  166. type = type?.BaseType;
  167. }
  168. return copy;
  169. }
  170. /// <summary>
  171. /// A generic version of <see cref="CopyComponent(Component, Type, GameObject, Type)"/>.
  172. /// </summary>
  173. /// <seealso cref="CopyComponent(Component, Type, GameObject, Type)"/>
  174. /// <typeparam name="T">the overriding type</typeparam>
  175. /// <param name="original">the original component</param>
  176. /// <param name="destination">the destination game object</param>
  177. /// <param name="originalTypeOverride">overrides the source component type (for example, to a superclass)</param>
  178. /// <returns>the copied component</returns>
  179. public static T CopyComponent<T>(this Component original, GameObject destination, Type originalTypeOverride = null)
  180. where T : Component
  181. {
  182. var copy = destination.AddComponent<T>();
  183. var originalType = originalTypeOverride ?? original.GetType();
  184. Type type = originalType;
  185. while (type != typeof(MonoBehaviour))
  186. {
  187. CopyForType(type, original, copy);
  188. type = type?.BaseType;
  189. }
  190. return copy;
  191. }
  192. private static void CopyForType(Type type, Component source, Component destination)
  193. {
  194. FieldInfo[] myObjectFields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
  195. foreach (FieldInfo fi in myObjectFields)
  196. {
  197. fi.SetValue(destination, fi.GetValue(source));
  198. }
  199. }
  200. }
  201. }