TypeUtilities.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. // Copyright (c) The Avalonia Project. All rights reserved.
  2. // Licensed under the MIT license. See licence.md file in the project root for full license information.
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Globalization;
  6. using System.Linq;
  7. using System.Reflection;
  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 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 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 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 = new[]
  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. var t = type.GetTypeInfo();
  91. return !t.IsValueType || (t.IsGenericType && (t.GetGenericTypeDefinition() == typeof(Nullable<>)));
  92. }
  93. /// <summary>
  94. /// Try to convert a value to a type by any means possible.
  95. /// </summary>
  96. /// <param name="to">The type to cast to.</param>
  97. /// <param name="value">The value to cast.</param>
  98. /// <param name="culture">The culture to use.</param>
  99. /// <param name="result">If sucessful, contains the cast value.</param>
  100. /// <returns>True if the cast was sucessful, otherwise false.</returns>
  101. public static bool TryConvert(Type to, object value, CultureInfo culture, out object result)
  102. {
  103. if (value == null)
  104. {
  105. result = null;
  106. return AcceptsNull(to);
  107. }
  108. if (value == AvaloniaProperty.UnsetValue)
  109. {
  110. result = value;
  111. return true;
  112. }
  113. var from = value.GetType();
  114. var fromTypeInfo = from.GetTypeInfo();
  115. var toTypeInfo = to.GetTypeInfo();
  116. if (toTypeInfo.IsAssignableFrom(fromTypeInfo))
  117. {
  118. result = value;
  119. return true;
  120. }
  121. if (to == typeof(string))
  122. {
  123. result = Convert.ToString(value);
  124. return true;
  125. }
  126. if (toTypeInfo.IsEnum && from == typeof(string))
  127. {
  128. if (Enum.IsDefined(to, (string)value))
  129. {
  130. result = Enum.Parse(to, (string)value);
  131. return true;
  132. }
  133. }
  134. if (!fromTypeInfo.IsEnum && toTypeInfo.IsEnum)
  135. {
  136. result = null;
  137. if (TryConvert(Enum.GetUnderlyingType(to), value, culture, out object enumValue))
  138. {
  139. result = Enum.ToObject(to, enumValue);
  140. return true;
  141. }
  142. }
  143. if (fromTypeInfo.IsEnum && IsNumeric(to))
  144. {
  145. try
  146. {
  147. result = Convert.ChangeType((int)value, to, culture);
  148. return true;
  149. }
  150. catch
  151. {
  152. result = null;
  153. return false;
  154. }
  155. }
  156. var convertableFrom = Array.IndexOf(InbuiltTypes, from);
  157. var convertableTo = Array.IndexOf(InbuiltTypes, to);
  158. if (convertableFrom != -1 && convertableTo != -1)
  159. {
  160. if ((Conversions[convertableFrom] & 1 << convertableTo) != 0)
  161. {
  162. try
  163. {
  164. result = Convert.ChangeType(value, to, culture);
  165. return true;
  166. }
  167. catch
  168. {
  169. result = null;
  170. return false;
  171. }
  172. }
  173. }
  174. var cast = from.GetRuntimeMethods()
  175. .FirstOrDefault(m => (m.Name == "op_Implicit" || m.Name == "op_Explicit") && m.ReturnType == to);
  176. if (cast != null)
  177. {
  178. result = cast.Invoke(null, new[] { value });
  179. return true;
  180. }
  181. result = null;
  182. return false;
  183. }
  184. /// <summary>
  185. /// Try to convert a value to a type using the implicit conversions allowed by the C#
  186. /// language.
  187. /// </summary>
  188. /// <param name="to">The type to cast to.</param>
  189. /// <param name="value">The value to cast.</param>
  190. /// <param name="result">If sucessful, contains the cast value.</param>
  191. /// <returns>True if the cast was sucessful, otherwise false.</returns>
  192. public static bool TryConvertImplicit(Type to, object value, out object result)
  193. {
  194. if (value == null)
  195. {
  196. result = null;
  197. return AcceptsNull(to);
  198. }
  199. if (value == AvaloniaProperty.UnsetValue)
  200. {
  201. result = value;
  202. return true;
  203. }
  204. var from = value.GetType();
  205. var fromTypeInfo = from.GetTypeInfo();
  206. var toTypeInfo = to.GetTypeInfo();
  207. if (toTypeInfo.IsAssignableFrom(fromTypeInfo))
  208. {
  209. result = value;
  210. return true;
  211. }
  212. var convertableFrom = Array.IndexOf(InbuiltTypes, from);
  213. var convertableTo = Array.IndexOf(InbuiltTypes, to);
  214. if (convertableFrom != -1 && convertableTo != -1)
  215. {
  216. if ((ImplicitConversions[convertableFrom] & 1 << convertableTo) != 0)
  217. {
  218. try
  219. {
  220. result = Convert.ChangeType(value, to, CultureInfo.InvariantCulture);
  221. return true;
  222. }
  223. catch
  224. {
  225. result = null;
  226. return false;
  227. }
  228. }
  229. }
  230. var cast = from.GetRuntimeMethods()
  231. .FirstOrDefault(m => m.Name == "op_Implicit" && m.ReturnType == to);
  232. if (cast != null)
  233. {
  234. result = cast.Invoke(null, new[] { value });
  235. return true;
  236. }
  237. result = null;
  238. return false;
  239. }
  240. /// <summary>
  241. /// Convert a value to a type by any means possible, returning the default for that type
  242. /// if the value could not be converted.
  243. /// </summary>
  244. /// <param name="value">The value to cast.</param>
  245. /// <param name="type">The type to cast to..</param>
  246. /// <param name="culture">The culture to use.</param>
  247. /// <returns>A value of <paramref name="type"/>.</returns>
  248. public static object ConvertOrDefault(object value, Type type, CultureInfo culture)
  249. {
  250. return TryConvert(type, value, culture, out object result) ? result : Default(type);
  251. }
  252. /// <summary>
  253. /// Convert a value to a type using the implicit conversions allowed by the C# language or
  254. /// return the default for the type if the value could not be converted.
  255. /// </summary>
  256. /// <param name="value">The value to cast.</param>
  257. /// <param name="type">The type to cast to..</param>
  258. /// <returns>A value of <paramref name="type"/>.</returns>
  259. public static object ConvertImplicitOrDefault(object value, Type type)
  260. {
  261. return TryConvertImplicit(type, value, out object result) ? result : Default(type);
  262. }
  263. /// <summary>
  264. /// Gets the default value for the specified type.
  265. /// </summary>
  266. /// <param name="type">The type.</param>
  267. /// <returns>The default value.</returns>
  268. public static object Default(Type type)
  269. {
  270. var typeInfo = type.GetTypeInfo();
  271. if (typeInfo.IsValueType)
  272. {
  273. return Activator.CreateInstance(type);
  274. }
  275. else
  276. {
  277. return null;
  278. }
  279. }
  280. /// <summary>
  281. /// Determines if a type is numeric. Nullable numeric types are considered numeric.
  282. /// </summary>
  283. /// <returns>
  284. /// True if the type is numberic; otherwise false.
  285. /// </returns>
  286. /// <remarks>
  287. /// Boolean is not considered numeric.
  288. /// </remarks>
  289. public static bool IsNumeric(Type type)
  290. {
  291. if (type == null)
  292. {
  293. return false;
  294. }
  295. if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
  296. {
  297. return IsNumeric(Nullable.GetUnderlyingType(type));
  298. }
  299. else
  300. {
  301. return NumericTypes.Contains(type);
  302. }
  303. }
  304. }
  305. }