TypeUtilities.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. using System;
  2. using System.ComponentModel;
  3. using System.Diagnostics.CodeAnalysis;
  4. using System.Globalization;
  5. using System.Linq;
  6. using System.Reflection;
  7. using System.Runtime.CompilerServices;
  8. namespace Avalonia.Utilities
  9. {
  10. /// <summary>
  11. /// Provides utilities for working with types at runtime.
  12. /// </summary>
  13. public static class TypeUtilities
  14. {
  15. private static readonly int[] Conversions =
  16. {
  17. 0b101111111111101, // Boolean
  18. 0b100001111111110, // Char
  19. 0b101111111111111, // SByte
  20. 0b101111111111111, // Byte
  21. 0b101111111111111, // Int16
  22. 0b101111111111111, // UInt16
  23. 0b101111111111111, // Int32
  24. 0b101111111111111, // UInt32
  25. 0b101111111111111, // Int64
  26. 0b101111111111111, // UInt64
  27. 0b101111111111101, // Single
  28. 0b101111111111101, // Double
  29. 0b101111111111101, // Decimal
  30. 0b110000000000000, // DateTime
  31. 0b111111111111111, // String
  32. };
  33. private static readonly int[] ImplicitConversions =
  34. {
  35. 0b000000000000001, // Boolean
  36. 0b001110111100010, // Char
  37. 0b001110101010100, // SByte
  38. 0b001111111111000, // Byte
  39. 0b001110101010000, // Int16
  40. 0b001111111100000, // UInt16
  41. 0b001110101000000, // Int32
  42. 0b001111110000000, // UInt32
  43. 0b001110100000000, // Int64
  44. 0b001111000000000, // UInt64
  45. 0b000110000000000, // Single
  46. 0b000100000000000, // Double
  47. 0b001000000000000, // Decimal
  48. 0b010000000000000, // DateTime
  49. 0b100000000000000, // String
  50. };
  51. private static readonly Type[] InbuiltTypes =
  52. {
  53. typeof(Boolean),
  54. typeof(Char),
  55. typeof(SByte),
  56. typeof(Byte),
  57. typeof(Int16),
  58. typeof(UInt16),
  59. typeof(Int32),
  60. typeof(UInt32),
  61. typeof(Int64),
  62. typeof(UInt64),
  63. typeof(Single),
  64. typeof(Double),
  65. typeof(Decimal),
  66. typeof(DateTime),
  67. typeof(String),
  68. };
  69. private static readonly Type[] NumericTypes =
  70. {
  71. typeof(Byte),
  72. typeof(Decimal),
  73. typeof(Double),
  74. typeof(Int16),
  75. typeof(Int32),
  76. typeof(Int64),
  77. typeof(SByte),
  78. typeof(Single),
  79. typeof(UInt16),
  80. typeof(UInt32),
  81. typeof(UInt64),
  82. };
  83. /// <summary>
  84. /// Returns a value indicating whether null can be assigned to the specified type.
  85. /// </summary>
  86. /// <param name="type">The type.</param>
  87. /// <returns>True if the type accepts null values; otherwise false.</returns>
  88. public static bool AcceptsNull(Type type)
  89. {
  90. return !type.IsValueType || IsNullableType(type);
  91. }
  92. /// <summary>
  93. /// Returns a value indicating whether null can be assigned to the specified type.
  94. /// </summary>
  95. /// <typeparam name="T">The type</typeparam>
  96. /// <returns>True if the type accepts null values; otherwise false.</returns>
  97. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  98. public static bool AcceptsNull<T>()
  99. {
  100. return default(T) is null;
  101. }
  102. /// <summary>
  103. /// Returns a value indicating whether value can be casted to the specified type.
  104. /// If value is null, checks if instances of that type can be null.
  105. /// </summary>
  106. /// <typeparam name="T">The type to cast to</typeparam>
  107. /// <param name="value">The value to check if cast possible</param>
  108. /// <returns>True if the cast is possible, otherwise false.</returns>
  109. public static bool CanCast<T>(object? value)
  110. {
  111. return value is T || (value is null && AcceptsNull<T>());
  112. }
  113. /// <summary>
  114. /// Try to convert a value to a type by any means possible.
  115. /// </summary>
  116. /// <param name="to">The type to convert to.</param>
  117. /// <param name="value">The value to convert.</param>
  118. /// <param name="culture">The culture to use.</param>
  119. /// <param name="result">If successful, contains the convert value.</param>
  120. /// <returns>True if the cast was successful, otherwise false.</returns>
  121. [RequiresUnreferencedCode(TrimmingMessages.TypeConvertionRequiresUnreferencedCodeMessage)]
  122. public static bool TryConvert(Type to, object? value, CultureInfo? culture, out object? result)
  123. {
  124. if (value == null)
  125. {
  126. result = null;
  127. return AcceptsNull(to);
  128. }
  129. if (value == AvaloniaProperty.UnsetValue)
  130. {
  131. result = value;
  132. return true;
  133. }
  134. var toUnderl = Nullable.GetUnderlyingType(to) ?? to;
  135. var from = value.GetType();
  136. if (toUnderl.IsAssignableFrom(from))
  137. {
  138. result = value;
  139. return true;
  140. }
  141. if (toUnderl == typeof(string))
  142. {
  143. result = Convert.ToString(value, culture)!;
  144. return true;
  145. }
  146. if (toUnderl.IsEnum && from == typeof(string))
  147. {
  148. if (Enum.IsDefined(toUnderl, (string)value))
  149. {
  150. result = Enum.Parse(toUnderl, (string)value);
  151. return true;
  152. }
  153. }
  154. if (!from.IsEnum && toUnderl.IsEnum)
  155. {
  156. result = null;
  157. if (TryConvert(Enum.GetUnderlyingType(toUnderl), value, culture, out var enumValue))
  158. {
  159. result = Enum.ToObject(toUnderl, enumValue!);
  160. return true;
  161. }
  162. }
  163. if (from.IsEnum && IsNumeric(toUnderl))
  164. {
  165. try
  166. {
  167. result = Convert.ChangeType((int)value, toUnderl, culture);
  168. return true;
  169. }
  170. catch
  171. {
  172. result = null;
  173. return false;
  174. }
  175. }
  176. var convertableFrom = Array.IndexOf(InbuiltTypes, from);
  177. var convertableTo = Array.IndexOf(InbuiltTypes, toUnderl);
  178. if (convertableFrom != -1 && convertableTo != -1)
  179. {
  180. if ((Conversions[convertableFrom] & 1 << convertableTo) != 0)
  181. {
  182. try
  183. {
  184. result = Convert.ChangeType(value, toUnderl, culture);
  185. return true;
  186. }
  187. catch
  188. {
  189. result = null;
  190. return false;
  191. }
  192. }
  193. }
  194. var toTypeConverter = TypeDescriptor.GetConverter(toUnderl);
  195. if (toTypeConverter.CanConvertFrom(from) == true)
  196. {
  197. result = toTypeConverter.ConvertFrom(null, culture, value);
  198. return true;
  199. }
  200. var fromTypeConverter = TypeDescriptor.GetConverter(from);
  201. if (fromTypeConverter.CanConvertTo(toUnderl) == true)
  202. {
  203. result = fromTypeConverter.ConvertTo(null, culture, value, toUnderl);
  204. return true;
  205. }
  206. var cast = FindTypeConversionOperatorMethod(from, toUnderl, OperatorType.Implicit | OperatorType.Explicit);
  207. if (cast != null)
  208. {
  209. result = cast.Invoke(null, new[] { value });
  210. return true;
  211. }
  212. result = null;
  213. return false;
  214. }
  215. /// <summary>
  216. /// Try to convert a value to a type using the implicit conversions allowed by the C#
  217. /// language.
  218. /// </summary>
  219. /// <param name="to">The type to convert to.</param>
  220. /// <param name="value">The value to convert.</param>
  221. /// <param name="result">If successful, contains the converted value.</param>
  222. /// <returns>True if the convert was successful, otherwise false.</returns>
  223. [RequiresUnreferencedCode(TrimmingMessages.ImplicitTypeConvertionRequiresUnreferencedCodeMessage)]
  224. public static bool TryConvertImplicit(Type to, object? value, out object? result)
  225. {
  226. if (value == null)
  227. {
  228. result = null;
  229. return AcceptsNull(to);
  230. }
  231. if (value == AvaloniaProperty.UnsetValue)
  232. {
  233. result = value;
  234. return true;
  235. }
  236. var from = value.GetType();
  237. if (to.IsAssignableFrom(from))
  238. {
  239. result = value;
  240. return true;
  241. }
  242. var convertableFrom = Array.IndexOf(InbuiltTypes, from);
  243. var convertableTo = Array.IndexOf(InbuiltTypes, to);
  244. if (convertableFrom != -1 && convertableTo != -1)
  245. {
  246. if ((ImplicitConversions[convertableFrom] & 1 << convertableTo) != 0)
  247. {
  248. try
  249. {
  250. result = Convert.ChangeType(value, to, CultureInfo.InvariantCulture);
  251. return true;
  252. }
  253. catch
  254. {
  255. result = null;
  256. return false;
  257. }
  258. }
  259. }
  260. var cast = FindTypeConversionOperatorMethod(from, to, OperatorType.Implicit);
  261. if (cast != null)
  262. {
  263. result = cast.Invoke(null, new[] { value });
  264. return true;
  265. }
  266. result = null;
  267. return false;
  268. }
  269. /// <summary>
  270. /// Convert a value to a type by any means possible, returning the default for that type
  271. /// if the value could not be converted.
  272. /// </summary>
  273. /// <param name="value">The value to convert.</param>
  274. /// <param name="type">The type to convert to..</param>
  275. /// <param name="culture">The culture to use.</param>
  276. /// <returns>A value of <paramref name="type"/>.</returns>
  277. [RequiresUnreferencedCode(TrimmingMessages.TypeConvertionRequiresUnreferencedCodeMessage)]
  278. public static object? ConvertOrDefault(object? value, Type type, CultureInfo culture)
  279. {
  280. return TryConvert(type, value, culture, out var result) ? result : Default(type);
  281. }
  282. /// <summary>
  283. /// Convert a value to a type using the implicit conversions allowed by the C# language or
  284. /// return the default for the type if the value could not be converted.
  285. /// </summary>
  286. /// <param name="value">The value to convert.</param>
  287. /// <param name="type">The type to convert to.</param>
  288. /// <returns>A value of <paramref name="type"/>.</returns>
  289. [RequiresUnreferencedCode(TrimmingMessages.ImplicitTypeConvertionRequiresUnreferencedCodeMessage)]
  290. public static object? ConvertImplicitOrDefault(object? value, Type type)
  291. {
  292. return TryConvertImplicit(type, value, out var result) ? result : Default(type);
  293. }
  294. [RequiresUnreferencedCode(TrimmingMessages.ImplicitTypeConvertionRequiresUnreferencedCodeMessage)]
  295. public static T ConvertImplicit<T>(object value)
  296. {
  297. if (TryConvertImplicit(typeof(T), value, out var result))
  298. {
  299. return (T)result!;
  300. }
  301. throw new InvalidCastException(
  302. $"Unable to convert object '{value ?? "(null)"}' of type '{value?.GetType()}' to type '{typeof(T)}'.");
  303. }
  304. /// <summary>
  305. /// Gets the default value for the specified type.
  306. /// </summary>
  307. /// <param name="type">The type.</param>
  308. /// <returns>The default value.</returns>
  309. [UnconditionalSuppressMessage("Trimming", "IL2067", Justification = "We don't care about public ctors for the value types, and always return null for the ref types.")]
  310. public static object? Default(Type type)
  311. {
  312. if (type.IsValueType)
  313. {
  314. return Activator.CreateInstance(type);
  315. }
  316. else
  317. {
  318. return null;
  319. }
  320. }
  321. /// <summary>
  322. /// Determines if a type is numeric. Nullable numeric types are considered numeric.
  323. /// </summary>
  324. /// <returns>
  325. /// True if the type is numeric; otherwise false.
  326. /// </returns>
  327. /// <remarks>
  328. /// Boolean is not considered numeric.
  329. /// </remarks>
  330. public static bool IsNumeric(Type type)
  331. {
  332. if (type == null)
  333. {
  334. return false;
  335. }
  336. var underlyingType = Nullable.GetUnderlyingType(type);
  337. if (underlyingType != null)
  338. {
  339. return IsNumeric(underlyingType);
  340. }
  341. else
  342. {
  343. return NumericTypes.Contains(type);
  344. }
  345. }
  346. [Flags]
  347. private enum OperatorType
  348. {
  349. Implicit = 1,
  350. Explicit = 2
  351. }
  352. private static bool IsNullableType(Type type)
  353. {
  354. return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
  355. }
  356. private static MethodInfo? FindTypeConversionOperatorMethod(
  357. [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type fromType,
  358. Type toType, OperatorType operatorType)
  359. {
  360. const string implicitName = "op_Implicit";
  361. const string explicitName = "op_Explicit";
  362. bool allowImplicit = operatorType.HasAllFlags(OperatorType.Implicit);
  363. bool allowExplicit = operatorType.HasAllFlags(OperatorType.Explicit);
  364. foreach (MethodInfo method in fromType.GetMethods())
  365. {
  366. if (!method.IsSpecialName || method.ReturnType != toType)
  367. {
  368. continue;
  369. }
  370. if (allowImplicit && method.Name == implicitName)
  371. {
  372. return method;
  373. }
  374. if (allowExplicit && method.Name == explicitName)
  375. {
  376. return method;
  377. }
  378. }
  379. return null;
  380. }
  381. }
  382. }