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);
}
}
}
}