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

PrivateObjectExtensions.cs 16 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. // <copyright file="PrivateObjectExtensions.cs">
  2. // Copyright (c) 2019 cactuaroid All Rights Reserved
  3. // </copyright>
  4. // <summary>
  5. // Released under the MIT license
  6. // https://github.com/cactuaroid/PrivateObjectExtensions
  7. // </summary>
  8. using Microsoft.VisualStudio.TestTools.UnitTesting;
  9. using System.Linq;
  10. using System.Reflection;
  11. namespace System
  12. {
  13. /// <summary>
  14. /// Extension methods for PrivateObject
  15. /// </summary>
  16. public static class PrivateObjectExtensions
  17. {
  18. private static readonly BindingFlags Static = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly | BindingFlags.Static;
  19. private static readonly BindingFlags Instance = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly | BindingFlags.Instance;
  20. /// <summary>
  21. /// Get from private (and any other) field/property.
  22. /// If the real type of specified object doesn't contain the specified field/property,
  23. /// base types are searched automatically.
  24. /// </summary>
  25. /// <param name="obj">The object to get from</param>
  26. /// <param name="name">The name of the field/property</param>
  27. /// <returns>The object got from the field/property</returns>
  28. /// <exception cref="ArgumentException">'name' is not found.</exception>
  29. /// <exception cref="ArgumentNullException">Arguments contain null.</exception>
  30. public static object GetPrivate(this object obj, string name)
  31. {
  32. if (obj == null) { throw new ArgumentNullException("obj"); }
  33. return GetPrivate(obj, name, obj.GetType(), null);
  34. }
  35. /// <summary>
  36. /// Get from private (and any other) field/property.
  37. /// If the real type of specified object doesn't contain the specified field/property,
  38. /// base types are searched automatically.
  39. /// </summary>
  40. /// <typeparam name="T">The type of the field/property</typeparam>
  41. /// <param name="obj">The object to get from</param>
  42. /// <param name="name">The name of the field/property</param>
  43. /// <returns>The object got from the field/property</returns>
  44. /// <exception cref="ArgumentException">'name' is not found.</exception>
  45. /// <exception cref="ArgumentNullException">Arguments contain null.</exception>
  46. public static T GetPrivate<T>(this object obj, string name)
  47. {
  48. if (obj == null) { throw new ArgumentNullException("obj"); }
  49. return (T)GetPrivate(obj, name, obj.GetType(), typeof(T));
  50. }
  51. /// <summary>
  52. /// Get from private (and any other) field/property with assuming the specified object as specified type.
  53. /// If the specified type doesn't contain the specified field/property,
  54. /// base types are searched automatically.
  55. /// </summary>
  56. /// <param name="obj">The object to get from</param>
  57. /// <param name="name">The name of the field/property</param>
  58. /// <param name="objType">The type of 'obj' for seaching member starting from. Real type of 'obj' is ignored.</param>
  59. /// <returns>The object got from the field/property</returns>
  60. /// <exception cref="ArgumentException">'name' is not found.</exception>
  61. /// <exception cref="ArgumentException">'objType' is not assignable from 'obj'.</exception>
  62. /// <exception cref="ArgumentNullException">Arguments contain null.</exception>
  63. public static object GetPrivate(this object obj, string name, Type objType)
  64. {
  65. return GetPrivate(obj, name, objType, null);
  66. }
  67. /// <summary>
  68. /// Get from private (and any other) field/property with assuming the specified object as specified type.
  69. /// If the specified type doesn't contain the specified field/property,
  70. /// base types are searched automatically.
  71. /// </summary>
  72. /// <typeparam name="T">The type of the field/property</typeparam>
  73. /// <param name="obj">The object to get from</param>
  74. /// <param name="name">The name of the field/property</param>
  75. /// <param name="objType">The type of 'obj' for seaching member starting from. Real type of 'obj' is ignored.</param>
  76. /// <returns>The object got from the field/property</returns>
  77. /// <exception cref="ArgumentException">'name' is not found.</exception>
  78. /// <exception cref="ArgumentException">'objType' is not assignable from 'obj'.</exception>
  79. /// <exception cref="ArgumentNullException">Arguments contain null.</exception>
  80. public static T GetPrivate<T>(this object obj, string name, Type objType)
  81. {
  82. return (T)GetPrivate(obj, name, objType, typeof(T));
  83. }
  84. private static object GetPrivate(object obj, string name, Type objType, Type memberType)
  85. {
  86. if (obj == null) { throw new ArgumentNullException("obj"); }
  87. if (name == null) { throw new ArgumentNullException("name"); }
  88. if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentException("name is empty or white-space.", "name"); }
  89. if (objType == null) { throw new ArgumentNullException("objType"); }
  90. if (!objType.IsAssignableFrom(obj.GetType())) { throw new ArgumentException($"{objType} is not assignable from {obj.GetType()}.", "objType"); }
  91. bool memberTypeMatching(Type actualType) => actualType == memberType;
  92. if (TryFindFieldOrPropertyOwnerType(objType, name, memberType, memberTypeMatching, Instance, out var ownerType))
  93. {
  94. return new PrivateObject(obj, new PrivateType(ownerType)).GetFieldOrProperty(name);
  95. }
  96. else if (TryFindFieldOrPropertyOwnerType(objType, name, memberType, memberTypeMatching, Static, out ownerType))
  97. {
  98. return new PrivateType(ownerType).GetStaticFieldOrProperty(name);
  99. }
  100. throw new ArgumentException(((memberType != null) ? memberType + " " : "") + name + " is not found.");
  101. }
  102. /// <summary>
  103. /// Get from private (and any other) static field/property.
  104. /// </summary>
  105. /// <param name="type">The type to get from</param>
  106. /// <param name="name">The name of the static field/property</param>
  107. /// <returns>The object got from the static field/property</returns>
  108. /// <exception cref="ArgumentException">'name' is not found.</exception>
  109. /// <exception cref="ArgumentNullException">Arguments contain null.</exception>
  110. public static object GetPrivate(this Type type, string name)
  111. {
  112. return GetPrivate(type, name, null);
  113. }
  114. /// <summary>
  115. /// Get from private (and any other) static field/property.
  116. /// </summary>
  117. /// <typeparam name="T">The type of the field/property</typeparam>
  118. /// <param name="type">The type to get from</param>
  119. /// <param name="name">The name of the static field/property</param>
  120. /// <returns>The object got from the static field/property</returns>
  121. /// <exception cref="ArgumentException">'name' is not found.</exception>
  122. /// <exception cref="ArgumentNullException">Arguments contain null.</exception>
  123. public static T GetPrivate<T>(this Type type, string name)
  124. {
  125. return (T)GetPrivate(type, name, typeof(T));
  126. }
  127. private static object GetPrivate(this Type type, string name, Type memberType)
  128. {
  129. if (type == null) { throw new ArgumentNullException("type"); }
  130. if (name == null) { throw new ArgumentNullException("name"); }
  131. if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentException("name is empty or white-space.", "name"); }
  132. bool memberTypeMatching(Type actualType) => actualType == memberType;
  133. if (type.ContainsFieldOrProperty(name, memberType, memberTypeMatching, Static))
  134. {
  135. return new PrivateType(type).GetStaticFieldOrProperty(name);
  136. }
  137. throw new ArgumentException(((memberType != null) ? memberType + " " : "") + name + " is not found.");
  138. }
  139. /// <summary>
  140. /// Set to private (and any other) field/property.
  141. /// If the real type of specified object doesn't contain the specified field/property,
  142. /// base types are searched automatically.
  143. /// </summary>
  144. /// <param name="obj">The object to set to</param>
  145. /// <param name="name">The name of the field/property</param>
  146. /// <param name="value">The value to set for 'name'</param>
  147. /// <exception cref="ArgumentException">'name' is not found.</exception>
  148. /// <exception cref="ArgumentNullException">Arguments contain null.</exception>
  149. public static void SetPrivate<T>(this object obj, string name, T value)
  150. {
  151. if (obj == null) { throw new ArgumentNullException("obj"); }
  152. SetPrivate(obj, name, value, obj.GetType());
  153. }
  154. /// <summary>
  155. /// Set to private (and any other) field/property with assuming the specified object as specified type.
  156. /// If the specified type doesn't contain the specified field/property,
  157. /// base types are searched automatically.
  158. /// </summary>
  159. /// <param name="obj">The object to set to</param>
  160. /// <param name="name">The name of the field/property</param>
  161. /// <param name="value">The value to set for 'name'</param>
  162. /// <param name="objType">The type of 'obj' for seaching member starting from. Real type of 'obj' is ignored.</param>
  163. /// <exception cref="ArgumentException">'name' is not found.</exception>
  164. /// <exception cref="ArgumentException">'objType' is not assignable from 'obj'.</exception>
  165. /// <exception cref="ArgumentNullException">Arguments contain null.</exception>
  166. public static void SetPrivate<T>(this object obj, string name, T value, Type objType)
  167. {
  168. if (obj == null) { throw new ArgumentNullException("obj"); }
  169. if (name == null) { throw new ArgumentNullException("name"); }
  170. if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentException("name is empty or white-space.", "name"); }
  171. if (value == null) { throw new ArgumentNullException("value"); }
  172. if (objType == null) { throw new ArgumentNullException("objType"); }
  173. if (!objType.IsAssignableFrom(obj.GetType())) { throw new ArgumentException($"{objType} is not assignable from {obj.GetType()}.", "objType"); }
  174. if (TrySetPrivate(obj, name, value, objType)) { return; }
  175. // retry for the case of getter only property
  176. if (TrySetPrivate(obj, GetBackingFieldName(name), value, objType)) { return; }
  177. throw new ArgumentException($"{typeof(T)} {name} is not found.");
  178. }
  179. private static bool TrySetPrivate<T>(object obj, string name, T value, Type objType)
  180. {
  181. var memberType = typeof(T);
  182. bool memberTypeMatching(Type actualType) => actualType.IsAssignableFrom(memberType);
  183. try
  184. {
  185. if (TryFindFieldOrPropertyOwnerType(objType, name, memberType, memberTypeMatching, Instance, out var ownerType))
  186. {
  187. new PrivateObject(obj, new PrivateType(ownerType)).SetFieldOrProperty(name, value);
  188. return true;
  189. }
  190. else if (TryFindFieldOrPropertyOwnerType(objType, name, memberType, memberTypeMatching, Static, out ownerType))
  191. {
  192. new PrivateType(ownerType).SetStaticFieldOrProperty(name, value);
  193. return true;
  194. }
  195. }
  196. catch (MissingMethodException)
  197. {
  198. // When getter only property name is given, the property is found but fails to set.
  199. return false;
  200. }
  201. return false;
  202. }
  203. /// <summary>
  204. /// Set to private (and any other) static field/property.
  205. /// </summary>
  206. /// <param name="type">The type to set to</param>
  207. /// <param name="name">The name of the field/property</param>
  208. /// <param name="value">The value to set for 'name'</param>
  209. /// <exception cref="ArgumentException">'name' is not found.</exception>
  210. /// <exception cref="ArgumentNullException">Arguments contain null.</exception>
  211. public static void SetPrivate<T>(this Type type, string name, T value)
  212. {
  213. if (type == null) { throw new ArgumentNullException("type"); }
  214. if (name == null) { throw new ArgumentNullException("name"); }
  215. if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentException("name is empty or white-space.", "name"); }
  216. if (TrySetPrivate(type, name, value)) { return; }
  217. // retry for the case of getter only property
  218. if (TrySetPrivate(type, GetBackingFieldName(name), value)) { return; }
  219. throw new ArgumentException($"{typeof(T)} {name} is not found.");
  220. }
  221. private static bool TrySetPrivate<T>(this Type type, string name, T value)
  222. {
  223. var memberType = typeof(T);
  224. bool memberTypeMatching(Type actualType) => actualType.IsAssignableFrom(memberType);
  225. try
  226. {
  227. if (type.ContainsFieldOrProperty(name, memberType, memberTypeMatching, Static))
  228. {
  229. new PrivateType(type).SetStaticFieldOrProperty(name, value);
  230. return true;
  231. }
  232. }
  233. catch (MissingMethodException)
  234. {
  235. // When getter only property name is given, the property is found but fails to set.
  236. return false;
  237. }
  238. return false;
  239. }
  240. private static string GetBackingFieldName(string propertyName)
  241. => $"<{propertyName}>k__BackingField"; // generated backing field name
  242. private static bool TryFindFieldOrPropertyOwnerType(Type objType, string name, Type memberType, Func<Type, bool> memberTypeMatching, BindingFlags bindingFlag, out Type ownerType)
  243. {
  244. ownerType = FindFieldOrPropertyOwnerType(objType, name, memberType, memberTypeMatching, bindingFlag);
  245. return (ownerType != null);
  246. }
  247. private static Type FindFieldOrPropertyOwnerType(Type objectType, string name, Type memberType, Func<Type, bool> memberTypeMatching, BindingFlags bindingFlags)
  248. {
  249. if (objectType == null) { return null; }
  250. if (objectType.ContainsFieldOrProperty(name, memberType, memberTypeMatching, bindingFlags))
  251. {
  252. return objectType;
  253. }
  254. return FindFieldOrPropertyOwnerType(objectType.BaseType, name, memberType, memberTypeMatching, bindingFlags);
  255. }
  256. private static bool ContainsFieldOrProperty(this Type objectType, string name, Type memberType, Func<Type, bool> memberTypeMatching, BindingFlags bindingFlags)
  257. {
  258. var fields = objectType
  259. .GetFields(bindingFlags)
  260. .Select((x) => new { Type = x.FieldType, Member = x as MemberInfo });
  261. var properties = objectType
  262. .GetProperties(bindingFlags)
  263. .Select((x) => new { Type = x.PropertyType, Member = x as MemberInfo });
  264. var members = fields.Concat(properties);
  265. return members.Any((actual) =>
  266. (memberType == null || memberTypeMatching.Invoke(actual.Type))
  267. && actual.Member.Name == name);
  268. }
  269. }
  270. }