RaspberryIO_26/Swan.Lite/FromString.cs
2019-12-08 19:54:52 +01:00

303 lines
13 KiB
C#

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