using System; using System.Collections.Concurrent; using System.ComponentModel; using System.Linq.Expressions; using System.Reflection; namespace Swan { /// /// Provides a standard way to convert strings to different types. /// public static class FromString { // It doesn't matter which converter we get here: ConvertFromInvariantString is not virtual. private static readonly MethodInfo ConvertFromInvariantStringMethod = new Func(TypeDescriptor.GetConverter(typeof(int)).ConvertFromInvariantString).Method; private static readonly MethodInfo TryConvertToInternalMethod = typeof(FromString).GetMethod(nameof(TryConvertToInternal), BindingFlags.Static | BindingFlags.NonPublic); private static readonly MethodInfo ConvertToInternalMethod = typeof(FromString).GetMethod(nameof(ConvertToInternal), BindingFlags.Static | BindingFlags.NonPublic); private static readonly ConcurrentDictionary> GenericTryConvertToMethods = new ConcurrentDictionary>(); private static readonly ConcurrentDictionary> GenericConvertToMethods = new ConcurrentDictionary>(); /// /// Determines whether a string can be converted to the specified type. /// /// The type resulting from the conversion. /// if the conversion is possible; /// otherwise, . /// is . public static bool CanConvertTo(Type type) => TypeDescriptor.GetConverter(type).CanConvertFrom(typeof(string)); /// /// Determines whether a string can be converted to the specified type. /// /// The type resulting from the conversion. /// if the conversion is possible; /// otherwise, . public static bool CanConvertTo() => TypeDescriptor.GetConverter(typeof(TResult)).CanConvertFrom(typeof(string)); /// /// Attempts to convert a string to the specified type. /// /// The type resulting from the conversion. /// The string to convert. /// When this method returns , /// the result of the conversion. This parameter is passed uninitialized. /// if the conversion is successful; /// otherwise, . /// is . public static bool TryConvertTo(Type type, string str, out object? result) { var converter = TypeDescriptor.GetConverter(type); if (!converter.CanConvertFrom(typeof(string))) { result = null; return false; } try { result = converter.ConvertFromInvariantString(str); return true; } catch (Exception e) when (!e.IsCriticalException()) { result = null; return false; } } /// /// Attempts to convert a string to the specified type. /// /// The type resulting from the conversion. /// The string to convert. /// When this method returns , /// the result of the conversion. This parameter is passed uninitialized. /// if the conversion is successful; /// otherwise, . public static bool TryConvertTo(string str, out TResult result) { var converter = TypeDescriptor.GetConverter(typeof(TResult)); if (!converter.CanConvertFrom(typeof(string))) { result = default; return false; } try { result = (TResult)converter.ConvertFromInvariantString(str); return true; } catch (Exception e) when (!e.IsCriticalException()) { result = default; return false; } } /// /// Converts a string to the specified type. /// /// The type resulting from the conversion. /// The string to convert. /// An instance of . /// is . /// The conversion was not successful. public static object ConvertTo(Type type, string str) { if (type == null) throw new ArgumentNullException(nameof(type)); try { return TypeDescriptor.GetConverter(type).ConvertFromInvariantString(str); } catch (Exception e) when (!e.IsCriticalException()) { throw new StringConversionException(type, e); } } /// /// Converts a string to the specified type. /// /// The type resulting from the conversion. /// The string to convert. /// An instance of . /// /// The conversion was not successful. /// public static TResult ConvertTo(string str) { try { return (TResult)TypeDescriptor.GetConverter(typeof(TResult)).ConvertFromInvariantString(str); } catch (Exception e) when (!e.IsCriticalException()) { throw new StringConversionException(typeof(TResult), e); } } /// /// Attempts to convert an array of strings to an array of the specified type. /// /// The type resulting from the conversion of each /// element of . /// The array to convert. /// When this method returns , /// the result of the conversion. This parameter is passed uninitialized. /// if the conversion is successful; /// otherwise, . /// is . public static bool TryConvertTo(Type type, string[] strings, out object? result) { if (strings == null) { result = null; return false; } var method = GenericTryConvertToMethods.GetOrAdd(type, BuildNonGenericTryConvertLambda); var (success, methodResult) = method(strings); result = methodResult; return success; } /// /// Attempts to convert an array of strings to an array of the specified type. /// /// The type resulting from the conversion of each /// element of . /// The array to convert. /// When this method returns , /// the result of the conversion. This parameter is passed uninitialized. /// if the conversion is successful; /// otherwise, . public static bool TryConvertTo(string[] strings, out TResult[]? result) { if (strings == null) { result = null; return false; } var converter = TypeDescriptor.GetConverter(typeof(TResult)); if (!converter.CanConvertFrom(typeof(string))) { result = null; return false; } try { result = new TResult[strings.Length]; var i = 0; foreach (var str in strings) result[i++] = (TResult)converter.ConvertFromInvariantString(str); return true; } catch (Exception e) when (!e.IsCriticalException()) { result = null; return false; } } /// /// Converts an array of strings to an array of the specified type. /// /// The type resulting from the conversion of each /// element of . /// The array to convert. /// An array of . /// is . /// The conversion of at least one /// of the elements of was not successful. public static object? ConvertTo(Type type, string[] strings) { if (strings == null) return null; var method = GenericConvertToMethods.GetOrAdd(type, BuildNonGenericConvertLambda); return method(strings); } /// /// Converts an array of strings to an array of the specified type. /// /// The type resulting from the conversion of each /// element of . /// The array to convert. /// An array of . /// The conversion of at least one /// of the elements of was not successful. public static TResult[]? ConvertTo(string[] strings) { if (strings == null) return null; var converter = TypeDescriptor.GetConverter(typeof(TResult)); var result = new TResult[strings.Length]; var i = 0; try { foreach (var str in strings) result[i++] = (TResult)converter.ConvertFromInvariantString(str); } catch (Exception e) when (!e.IsCriticalException()) { throw new StringConversionException(typeof(TResult), e); } return result; } /// /// Converts a expression, if the type can be converted to string, to a new expression including /// the conversion to string. /// /// The type. /// The string. /// A new expression where the previous expression is converted to string. public static Expression? ConvertExpressionTo(Type type, Expression str) { var converter = TypeDescriptor.GetConverter(type); return converter.CanConvertFrom(typeof(string)) ? Expression.Convert( Expression.Call(Expression.Constant(converter), ConvertFromInvariantStringMethod, str), type) : null; } private static Func BuildNonGenericTryConvertLambda(Type type) { var methodInfo = TryConvertToInternalMethod.MakeGenericMethod(type); var parameter = Expression.Parameter(typeof(string[])); var body = Expression.Call(methodInfo, parameter); var lambda = Expression.Lambda>(body, parameter); return lambda.Compile(); } private static (bool Success, object? Result) TryConvertToInternal(string[] strings) { var converter = TypeDescriptor.GetConverter(typeof(TResult)); if (!converter.CanConvertFrom(typeof(string))) return (false, null); var result = new TResult[strings.Length]; var i = 0; try { foreach (var str in strings) result[i++] = (TResult)converter.ConvertFromInvariantString(str); return (true, result); } catch (Exception e) when (!e.IsCriticalException()) { return (false, null); } } private static Func BuildNonGenericConvertLambda(Type type) { var methodInfo = ConvertToInternalMethod.MakeGenericMethod(type); var parameter = Expression.Parameter(typeof(string[])); var body = Expression.Call(methodInfo, parameter); var lambda = Expression.Lambda>(body, parameter); return lambda.Compile(); } private static object ConvertToInternal(string[] strings) { var converter = TypeDescriptor.GetConverter(typeof(TResult)); var result = new TResult[strings.Length]; var i = 0; try { foreach (var str in strings) result[i++] = (TResult)converter.ConvertFromInvariantString(str); return result; } catch (Exception e) when (!e.IsCriticalException()) { throw new StringConversionException(typeof(TResult), e); } } } }