// Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Reflection; namespace Avalonia.Utilities { /// /// Provides utilities for working with types at runtime. /// public static class TypeUtilities { private static int[] Conversions = { 0b101111111111101, // Boolean 0b100001111111110, // Char 0b101111111111111, // SByte 0b101111111111111, // Byte 0b101111111111111, // Int16 0b101111111111111, // UInt16 0b101111111111111, // Int32 0b101111111111111, // UInt32 0b101111111111111, // Int64 0b101111111111111, // UInt64 0b101111111111101, // Single 0b101111111111101, // Double 0b101111111111101, // Decimal 0b110000000000000, // DateTime 0b111111111111111, // String }; private static int[] ImplicitConversions = { 0b000000000000001, // Boolean 0b001110111100010, // Char 0b001110101010100, // SByte 0b001111111111000, // Byte 0b001110101010000, // Int16 0b001111111100000, // UInt16 0b001110101000000, // Int32 0b001111110000000, // UInt32 0b001110100000000, // Int64 0b001111000000000, // UInt64 0b000110000000000, // Single 0b000100000000000, // Double 0b001000000000000, // Decimal 0b010000000000000, // DateTime 0b100000000000000, // String }; private static Type[] InbuiltTypes = { typeof(Boolean), typeof(Char), typeof(SByte), typeof(Byte), typeof(Int16), typeof(UInt16), typeof(Int32), typeof(UInt32), typeof(Int64), typeof(UInt64), typeof(Single), typeof(Double), typeof(Decimal), typeof(DateTime), typeof(String), }; private static readonly Type[] NumericTypes = new[] { typeof(Byte), typeof(Decimal), typeof(Double), typeof(Int16), typeof(Int32), typeof(Int64), typeof(SByte), typeof(Single), typeof(UInt16), typeof(UInt32), typeof(UInt64), }; /// /// Returns a value indicating whether null can be assigned to the specified type. /// /// The type. /// True if the type accepts null values; otherwise false. public static bool AcceptsNull(Type type) { var t = type.GetTypeInfo(); return !t.IsValueType || (t.IsGenericType && (t.GetGenericTypeDefinition() == typeof(Nullable<>))); } /// /// Try to convert a value to a type by any means possible. /// /// The type to cast to. /// The value to cast. /// The culture to use. /// If sucessful, contains the cast value. /// True if the cast was sucessful, otherwise false. public static bool TryConvert(Type to, object value, CultureInfo culture, out object result) { if (value == null) { result = null; return AcceptsNull(to); } if (value == AvaloniaProperty.UnsetValue) { result = value; return true; } var from = value.GetType(); var fromTypeInfo = from.GetTypeInfo(); var toTypeInfo = to.GetTypeInfo(); if (toTypeInfo.IsAssignableFrom(fromTypeInfo)) { result = value; return true; } if (to == typeof(string)) { result = Convert.ToString(value); return true; } if (toTypeInfo.IsEnum && from == typeof(string)) { if (Enum.IsDefined(to, (string)value)) { result = Enum.Parse(to, (string)value); return true; } } if (!fromTypeInfo.IsEnum && toTypeInfo.IsEnum) { result = null; if (TryConvert(Enum.GetUnderlyingType(to), value, culture, out object enumValue)) { result = Enum.ToObject(to, enumValue); return true; } } if (fromTypeInfo.IsEnum && IsNumeric(to)) { try { result = Convert.ChangeType((int)value, to, culture); return true; } catch { result = null; return false; } } var convertableFrom = Array.IndexOf(InbuiltTypes, from); var convertableTo = Array.IndexOf(InbuiltTypes, to); if (convertableFrom != -1 && convertableTo != -1) { if ((Conversions[convertableFrom] & 1 << convertableTo) != 0) { try { result = Convert.ChangeType(value, to, culture); return true; } catch { result = null; return false; } } } var cast = from.GetRuntimeMethods() .FirstOrDefault(m => (m.Name == "op_Implicit" || m.Name == "op_Explicit") && m.ReturnType == to); if (cast != null) { result = cast.Invoke(null, new[] { value }); return true; } result = null; return false; } /// /// Try to convert a value to a type using the implicit conversions allowed by the C# /// language. /// /// The type to cast to. /// The value to cast. /// If sucessful, contains the cast value. /// True if the cast was sucessful, otherwise false. public static bool TryConvertImplicit(Type to, object value, out object result) { if (value == null) { result = null; return AcceptsNull(to); } if (value == AvaloniaProperty.UnsetValue) { result = value; return true; } var from = value.GetType(); var fromTypeInfo = from.GetTypeInfo(); var toTypeInfo = to.GetTypeInfo(); if (toTypeInfo.IsAssignableFrom(fromTypeInfo)) { result = value; return true; } var convertableFrom = Array.IndexOf(InbuiltTypes, from); var convertableTo = Array.IndexOf(InbuiltTypes, to); if (convertableFrom != -1 && convertableTo != -1) { if ((ImplicitConversions[convertableFrom] & 1 << convertableTo) != 0) { try { result = Convert.ChangeType(value, to, CultureInfo.InvariantCulture); return true; } catch { result = null; return false; } } } var cast = from.GetRuntimeMethods() .FirstOrDefault(m => m.Name == "op_Implicit" && m.ReturnType == to); if (cast != null) { result = cast.Invoke(null, new[] { value }); return true; } result = null; return false; } /// /// Convert a value to a type by any means possible, returning the default for that type /// if the value could not be converted. /// /// The value to cast. /// The type to cast to.. /// The culture to use. /// A value of . public static object ConvertOrDefault(object value, Type type, CultureInfo culture) { return TryConvert(type, value, culture, out object result) ? result : Default(type); } /// /// Convert a value to a type using the implicit conversions allowed by the C# language or /// return the default for the type if the value could not be converted. /// /// The value to cast. /// The type to cast to.. /// A value of . public static object ConvertImplicitOrDefault(object value, Type type) { return TryConvertImplicit(type, value, out object result) ? result : Default(type); } /// /// Gets the default value for the specified type. /// /// The type. /// The default value. public static object Default(Type type) { var typeInfo = type.GetTypeInfo(); if (typeInfo.IsValueType) { return Activator.CreateInstance(type); } else { return null; } } /// /// Determines if a type is numeric. Nullable numeric types are considered numeric. /// /// /// True if the type is numberic; otherwise false. /// /// /// Boolean is not considered numeric. /// public static bool IsNumeric(Type type) { if (type == null) { return false; } if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) { return IsNumeric(Nullable.GetUnderlyingType(type)); } else { return NumericTypes.Contains(type); } } } }