PageRenderTime 56ms CodeModel.GetById 23ms app.highlight 27ms RepoModel.GetById 1ms 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
  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}