TypeUtilities.cs 12 KB

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