| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432 |
- 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
- {
- /// <summary>
- /// Provides utilities for working with types at runtime.
- /// </summary>
- 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),
- };
- /// <summary>
- /// Returns a value indicating whether null can be assigned to the specified type.
- /// </summary>
- /// <param name="type">The type.</param>
- /// <returns>True if the type accepts null values; otherwise false.</returns>
- public static bool AcceptsNull(Type type)
- {
- return !type.IsValueType || IsNullableType(type);
- }
- /// <summary>
- /// Returns a value indicating whether null can be assigned to the specified type.
- /// </summary>
- /// <typeparam name="T">The type</typeparam>
- /// <returns>True if the type accepts null values; otherwise false.</returns>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static bool AcceptsNull<T>()
- {
- return default(T) is null;
- }
- /// <summary>
- /// 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.
- /// </summary>
- /// <typeparam name="T">The type to cast to</typeparam>
- /// <param name="value">The value to check if cast possible</param>
- /// <returns>True if the cast is possible, otherwise false.</returns>
- public static bool CanCast<T>(object? value)
- {
- return value is T || (value is null && AcceptsNull<T>());
- }
- /// <summary>
- /// Try to convert a value to a type by any means possible.
- /// </summary>
- /// <param name="to">The type to convert to.</param>
- /// <param name="value">The value to convert.</param>
- /// <param name="culture">The culture to use.</param>
- /// <param name="result">If successful, contains the convert value.</param>
- /// <returns>True if the cast was successful, otherwise false.</returns>
- [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;
- }
- /// <summary>
- /// Try to convert a value to a type using the implicit conversions allowed by the C#
- /// language.
- /// </summary>
- /// <param name="to">The type to convert to.</param>
- /// <param name="value">The value to convert.</param>
- /// <param name="result">If successful, contains the converted value.</param>
- /// <returns>True if the convert was successful, otherwise false.</returns>
- [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;
- }
- /// <summary>
- /// Convert a value to a type by any means possible, returning the default for that type
- /// if the value could not be converted.
- /// </summary>
- /// <param name="value">The value to convert.</param>
- /// <param name="type">The type to convert to..</param>
- /// <param name="culture">The culture to use.</param>
- /// <returns>A value of <paramref name="type"/>.</returns>
- [RequiresUnreferencedCode(TrimmingMessages.TypeConvertionRequiresUnreferencedCodeMessage)]
- public static object? ConvertOrDefault(object? value, Type type, CultureInfo culture)
- {
- return TryConvert(type, value, culture, out var result) ? result : Default(type);
- }
- /// <summary>
- /// 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.
- /// </summary>
- /// <param name="value">The value to convert.</param>
- /// <param name="type">The type to convert to.</param>
- /// <returns>A value of <paramref name="type"/>.</returns>
- [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<T>(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)}'.");
- }
- /// <summary>
- /// Gets the default value for the specified type.
- /// </summary>
- /// <param name="type">The type.</param>
- /// <returns>The default value.</returns>
- [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;
- }
- }
- /// <summary>
- /// Determines if a type is numeric. Nullable numeric types are considered numeric.
- /// </summary>
- /// <returns>
- /// True if the type is numeric; otherwise false.
- /// </returns>
- /// <remarks>
- /// Boolean is not considered numeric.
- /// </remarks>
- 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;
- }
- }
- }
|