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