PageRenderTime 57ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Avalonia.Base/Utilities/TypeUtilities.cs

https://gitlab.com/kush/Avalonia
C# | 339 lines | 249 code | 35 blank | 55 comment | 54 complexity | f4d03e58bb3c603a8540ce1b89f88aa1 MD5 | raw file
  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 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 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 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 = new[]
  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 = from.GetRuntimeMethods()
  174. .FirstOrDefault(m => (m.Name == "op_Implicit" || m.Name == "op_Explicit") && m.ReturnType == to);
  175. if (cast != null)
  176. {
  177. result = cast.Invoke(null, new[] { value });
  178. return true;
  179. }
  180. result = null;
  181. return false;
  182. }
  183. /// <summary>
  184. /// Try to convert a value to a type using the implicit conversions allowed by the C#
  185. /// language.
  186. /// </summary>
  187. /// <param name="to">The type to cast to.</param>
  188. /// <param name="value">The value to cast.</param>
  189. /// <param name="result">If successful, contains the cast value.</param>
  190. /// <returns>True if the cast was successful, otherwise false.</returns>
  191. public static bool TryConvertImplicit(Type to, object value, out object result)
  192. {
  193. if (value == null)
  194. {
  195. result = null;
  196. return AcceptsNull(to);
  197. }
  198. if (value == AvaloniaProperty.UnsetValue)
  199. {
  200. result = value;
  201. return true;
  202. }
  203. var from = value.GetType();
  204. var fromTypeInfo = from.GetTypeInfo();
  205. var toTypeInfo = to.GetTypeInfo();
  206. if (toTypeInfo.IsAssignableFrom(fromTypeInfo))
  207. {
  208. result = value;
  209. return true;
  210. }
  211. var convertableFrom = Array.IndexOf(InbuiltTypes, from);
  212. var convertableTo = Array.IndexOf(InbuiltTypes, to);
  213. if (convertableFrom != -1 && convertableTo != -1)
  214. {
  215. if ((ImplicitConversions[convertableFrom] & 1 << convertableTo) != 0)
  216. {
  217. try
  218. {
  219. result = Convert.ChangeType(value, to, CultureInfo.InvariantCulture);
  220. return true;
  221. }
  222. catch
  223. {
  224. result = null;
  225. return false;
  226. }
  227. }
  228. }
  229. var cast = from.GetRuntimeMethods()
  230. .FirstOrDefault(m => m.Name == "op_Implicit" && m.ReturnType == to);
  231. if (cast != null)
  232. {
  233. result = cast.Invoke(null, new[] { value });
  234. return true;
  235. }
  236. result = null;
  237. return false;
  238. }
  239. /// <summary>
  240. /// Convert a value to a type by any means possible, returning the default for that type
  241. /// if the value could not be converted.
  242. /// </summary>
  243. /// <param name="value">The value to cast.</param>
  244. /// <param name="type">The type to cast to..</param>
  245. /// <param name="culture">The culture to use.</param>
  246. /// <returns>A value of <paramref name="type"/>.</returns>
  247. public static object ConvertOrDefault(object value, Type type, CultureInfo culture)
  248. {
  249. return TryConvert(type, value, culture, out object result) ? result : Default(type);
  250. }
  251. /// <summary>
  252. /// Convert a value to a type using the implicit conversions allowed by the C# language or
  253. /// return the default for the type if the value could not be converted.
  254. /// </summary>
  255. /// <param name="value">The value to cast.</param>
  256. /// <param name="type">The type to cast to..</param>
  257. /// <returns>A value of <paramref name="type"/>.</returns>
  258. public static object ConvertImplicitOrDefault(object value, Type type)
  259. {
  260. return TryConvertImplicit(type, value, out object result) ? result : Default(type);
  261. }
  262. /// <summary>
  263. /// Gets the default value for the specified type.
  264. /// </summary>
  265. /// <param name="type">The type.</param>
  266. /// <returns>The default value.</returns>
  267. public static object Default(Type type)
  268. {
  269. var typeInfo = type.GetTypeInfo();
  270. if (typeInfo.IsValueType)
  271. {
  272. return Activator.CreateInstance(type);
  273. }
  274. else
  275. {
  276. return null;
  277. }
  278. }
  279. /// <summary>
  280. /// Determines if a type is numeric. Nullable numeric types are considered numeric.
  281. /// </summary>
  282. /// <returns>
  283. /// True if the type is numeric; otherwise false.
  284. /// </returns>
  285. /// <remarks>
  286. /// Boolean is not considered numeric.
  287. /// </remarks>
  288. public static bool IsNumeric(Type type)
  289. {
  290. if (type == null)
  291. {
  292. return false;
  293. }
  294. if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
  295. {
  296. return IsNumeric(Nullable.GetUnderlyingType(type));
  297. }
  298. else
  299. {
  300. return NumericTypes.Contains(type);
  301. }
  302. }
  303. }
  304. }