using System; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; namespace Avalonia.Utilities { /// /// Provides utilities for working with types at runtime. /// public static class TypeUtilities { private static readonly 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 readonly 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 readonly 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 = { 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) { return !type.IsValueType || IsNullableType(type); } /// /// Returns a value indicating whether null can be assigned to the specified type. /// /// The type /// True if the type accepts null values; otherwise false. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool AcceptsNull() { return default(T) is null; } /// /// Returns a value indicating whether value can be casted to the specified type. /// If value is null, checks if instances of that type can be null. /// /// The type to cast to /// The value to check if cast possible /// True if the cast is possible, otherwise false. public static bool CanCast(object? value) { return value is T || (value is null && AcceptsNull()); } /// /// Try to convert a value to a type by any means possible. /// /// The type to convert to. /// The value to convert. /// The culture to use. /// If successful, contains the convert value. /// True if the cast was successful, otherwise false. [RequiresUnreferencedCode(TrimmingMessages.TypeConvertionRequiresUnreferencedCodeMessage)] 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 toUnderl = Nullable.GetUnderlyingType(to) ?? to; var from = value.GetType(); if (toUnderl.IsAssignableFrom(from)) { result = value; return true; } if (toUnderl == typeof(string)) { result = Convert.ToString(value, culture)!; return true; } if (toUnderl.IsEnum && from == typeof(string)) { if (Enum.IsDefined(toUnderl, (string)value)) { result = Enum.Parse(toUnderl, (string)value); return true; } } if (!from.IsEnum && toUnderl.IsEnum) { result = null; if (TryConvert(Enum.GetUnderlyingType(toUnderl), value, culture, out var enumValue)) { result = Enum.ToObject(toUnderl, enumValue!); return true; } } if (from.IsEnum && IsNumeric(toUnderl)) { try { result = Convert.ChangeType((int)value, toUnderl, culture); return true; } catch { result = null; return false; } } var convertableFrom = Array.IndexOf(InbuiltTypes, from); var convertableTo = Array.IndexOf(InbuiltTypes, toUnderl); if (convertableFrom != -1 && convertableTo != -1) { if ((Conversions[convertableFrom] & 1 << convertableTo) != 0) { try { result = Convert.ChangeType(value, toUnderl, culture); return true; } catch { result = null; return false; } } } var toTypeConverter = TypeDescriptor.GetConverter(toUnderl); if (toTypeConverter.CanConvertFrom(from) == true) { result = toTypeConverter.ConvertFrom(null, culture, value); return true; } var fromTypeConverter = TypeDescriptor.GetConverter(from); if (fromTypeConverter.CanConvertTo(toUnderl) == true) { result = fromTypeConverter.ConvertTo(null, culture, value, toUnderl); return true; } var cast = FindTypeConversionOperatorMethod(from, toUnderl, OperatorType.Implicit | OperatorType.Explicit); 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 convert to. /// The value to convert. /// If successful, contains the converted value. /// True if the convert was successful, otherwise false. [RequiresUnreferencedCode(TrimmingMessages.ImplicitTypeConvertionRequiresUnreferencedCodeMessage)] 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(); if (to.IsAssignableFrom(from)) { 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 = FindTypeConversionOperatorMethod(from, to, OperatorType.Implicit); 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 convert. /// The type to convert to.. /// The culture to use. /// A value of . [RequiresUnreferencedCode(TrimmingMessages.TypeConvertionRequiresUnreferencedCodeMessage)] public static object? ConvertOrDefault(object? value, Type type, CultureInfo culture) { return TryConvert(type, value, culture, out var 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 convert. /// The type to convert to. /// A value of . [RequiresUnreferencedCode(TrimmingMessages.ImplicitTypeConvertionRequiresUnreferencedCodeMessage)] public static object? ConvertImplicitOrDefault(object? value, Type type) { return TryConvertImplicit(type, value, out var result) ? result : Default(type); } [RequiresUnreferencedCode(TrimmingMessages.ImplicitTypeConvertionRequiresUnreferencedCodeMessage)] public static T ConvertImplicit(object value) { if (TryConvertImplicit(typeof(T), value, out var result)) { return (T)result!; } throw new InvalidCastException( $"Unable to convert object '{value ?? "(null)"}' of type '{value?.GetType()}' to type '{typeof(T)}'."); } /// /// Gets the default value for the specified type. /// /// The type. /// The default value. [UnconditionalSuppressMessage("Trimming", "IL2067", Justification = "We don't care about public ctors for the value types, and always return null for the ref types.")] public static object? Default(Type type) { if (type.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 numeric; otherwise false. /// /// /// Boolean is not considered numeric. /// public static bool IsNumeric(Type type) { if (type == null) { return false; } var underlyingType = Nullable.GetUnderlyingType(type); if (underlyingType != null) { return IsNumeric(underlyingType); } else { return NumericTypes.Contains(type); } } [Flags] private enum OperatorType { Implicit = 1, Explicit = 2 } private static bool IsNullableType(Type type) { return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); } private static MethodInfo? FindTypeConversionOperatorMethod( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type fromType, Type toType, OperatorType operatorType) { const string implicitName = "op_Implicit"; const string explicitName = "op_Explicit"; bool allowImplicit = operatorType.HasAllFlags(OperatorType.Implicit); bool allowExplicit = operatorType.HasAllFlags(OperatorType.Explicit); foreach (MethodInfo method in fromType.GetMethods()) { if (!method.IsSpecialName || method.ReturnType != toType) { continue; } if (allowImplicit && method.Name == implicitName) { return method; } if (allowExplicit && method.Name == explicitName) { return method; } } return null; } } }