/// <summary> /// Test if the input value is a non-null numeric type /// or a string that can be parsed as a number, with detection /// of hex strings controlled by <paramref name="parseFlags"/> /// </summary> /// <param name="value"></param> /// <param name="parseFlags"></param> public static bool IsNumeric(object value, ParseNumericStringFlags parseFlags) { if (null == value) { return(false); } if (value is string sValue) { if (double.TryParse(sValue, out _)) { return(true); } bool allowDigitSep = (parseFlags & ParseNumericStringFlags.AllowDigitSeparator) != 0; if (allowDigitSep) { bool changed = false; while (sValue.IndexOf('_') >= 0) { sValue = Regex.Replace(sValue, "([0-9a-fA-F])_+([0-9a-fA-F])", "$1$2"); changed = true; } if (changed && double.TryParse(sValue, out _)) { return(true); } } if ((parseFlags & ParseNumericStringFlags.HexString) != 0) { if (Regex.IsMatch(sValue, @"^\s*0[xX][0-9a-fA-F]+$")) { return(true); } } if ((parseFlags & ParseNumericStringFlags.OctalString) != 0) { if (Regex.IsMatch(sValue, @"^\s*0[oO][0-7]+$")) { return(true); } } if ((parseFlags & ParseNumericStringFlags.BinaryString) != 0) { if (Regex.IsMatch(sValue, @"^\s*0[bB][01]+$")) { return(true); } } return(false); } return(TypeInspection.IsNumberType(value.GetType())); }
private static TimeSpan _ToTimeSpan(object value) { if (value is TimeSpan) { return((TimeSpan)value); } if (value is string && Regex.IsMatch((string)value, @"^\s*\d+\.\d+\s*$")) { // Treat decimal string as seconds, not ticks return(TimeSpan.FromSeconds(To <double>(value))); } else if ((value != null && TypeInspection.IsNumberType(value.GetType())) || ValueInspection.IsNumeric(value)) { return(TimeSpan.FromTicks(To <long>(value))); } else if (value is string) { string sValue = (string)value; var m = Regex.Match(sValue, RX_MINUTES); if (m.Success) { // The input is a minutes : seconds string. The built-in TimeSpan.Parse requires a leading // hours segment as well. Append the 0: hours segment, and also pad the minutes segment with // a leading 0 if necessary sValue = "0:" + (m.Groups["minutes"].Value.Length == 2 ? "" : "0") + sValue; } return(TimeSpan.Parse(sValue)); } else if (value is DateTime) { return(TimeSpan.FromTicks(((DateTime)value).Ticks)); } // If the input is not a TimeSpan, this will throw an invalid cast exception in the localized language: return((TimeSpan)value); }
/// <summary> /// Test if the input value is a non-null numeric type /// or a string that can be parsed as a number, with detection /// of hex strings controlled by ConvertOptions flags /// </summary> /// <param name="value"></param> /// <param name="options"></param> public static bool IsNumeric(object value, ConvertOptions options) { if (value == null) { return(false); } if (value is string sValue) { if (double.TryParse(sValue, out double d)) { return(true); } if (options.HasFlag(ConvertOptions.AllowVBHex) && Regex.IsMatch(sValue, @"^\s*&[hH][0-9a-fA-F]+$")) { return(true); } if (options.HasFlag(ConvertOptions.Allow0xHex) && Regex.IsMatch(sValue, @"^\s*0[xX][0-9a-fA-F]+$")) { return(true); } return(false); } return(TypeInspection.IsNumberType(value.GetType())); }
internal static object To(object value, Type targetType, ConvertOptions options, bool ignoreError, object defaultValue) { var typeInfo = targetType.GetTypeInfo(); // Quick check #1: If input is null reference and target type is a reference type we can always safely return nothing if ((value == null) && (!typeInfo.IsValueType)) { return(null); } // Quick check #2: If the input already is of the target type the return it immediately if (targetType.IsInstanceOfType(value)) { return(value); } if (typeInfo.IsValueType) { // --------------------------------------------------------------------------- // Special treatment of Nullable<T>. Although both C# and VB allow assignment // and comparison of null literal to Nullable<T> values, the underlying type // is actually a non-nullable struct (value type), thus we handle nullable // types inside the section that handles value types // --------------------------------------------------------------------------- if (TypeInspection.IsNullableOfT(targetType)) { if (ValueInspection.IsNull(value, options)) { // Nullable<T> of T is technically a struct, so IsValueType is true. However, // semantics and implementation allow it to be treated as a reference type, // so return null if the input is empty return(null); } else { // Input is not an empty value, so convert to the underying type try { // Note: DON'T ignore error on underlying conversion type return(To(value, Nullable.GetUnderlyingType(targetType), options, false, null)); } catch { if (ignoreError) { return(defaultValue ?? null); } throw; } } } // end IsNullable<T> // If input is empty, we can immediately return default or throw an exception if (ValueInspection.IsNull(value, options)) { if (options.HasFlag(ConvertOptions.NullToValueDefault)) { //This is how we return the default value of a value type: return(defaultValue ?? Activator.CreateInstance(targetType)); } else { if (ignoreError) { return(defaultValue ?? Activator.CreateInstance(targetType)); } throw new ArgumentNullException("value", "Cannot convert empty input value to value type " + targetType.FullName); } } // We only got this far if the target type is a value type and the input value is not empty try { // --------------------------------------------------------------------------- // Special treatment of Enums // --------------------------------------------------------------------------- if (typeInfo.IsEnum) { // Test IsNumeric BEFORE testing string so that numeric strings are // treated as numbers, not as enum member names if (ValueInspection.IsNumeric(value, options)) { // If the input is a number or *numeric string*, first convert the // input to an enum number value, then cast it using Enum.ToObject // Note: DON'T ignore error on underlying conversion type var rawValue = To(value, Enum.GetUnderlyingType(targetType), options, false, null); return(Enum.ToObject(targetType, rawValue)); } else if (value is string) { // Input is a string but Information.IsNumeric did not recognize it // as a number. So, treat the input as an enum member name string sEnumName = Regex.Replace((string)value, @"[\s\r\n]+", ""); return(Enum.Parse(targetType, sEnumName, true)); } else { // Fallback: Attempt to convert the input to the underlying numeric type, even if // Information.IsNumeric returned false // Note: DON'T ignore error on underlying conversion type var rawValue = To(value, Enum.GetUnderlyingType(targetType), options, false, null); return(Enum.ToObject(targetType, rawValue)); } } // end IsEnum // --------------------------------------------------------------------------- // Use hand-written conversion functions for specific types // --------------------------------------------------------------------------- if (targetType == typeof(Guid)) { // Use special conversion logic for converting to a Guid return(_ToGuid(value)); } else if (targetType == typeof(TimeSpan)) { // Use special conversion logic for converting to a TimeSpan return(_ToTimeSpan(value)); } else if (targetType == typeof(bool)) { // Use special conversion logic for converting to Boolean return(_ToBool(value)); } // --------------------------------------------------------------------------- // Invoke IConvertible implementation, if any // --------------------------------------------------------------------------- if (value is IConvertible iConvertible) { // Use the System.ChangeType method, which makes full use of any IConvertible implementation on the target type try { return(System.Convert.ChangeType(value, targetType)); } catch { // Ignore exception and fall back to VBConvert implementation } } // --------------------------------------------------------------------------- // Fall back to VBConvert methods // --------------------------------------------------------------------------- switch (System.Convert.GetTypeCode(targetType)) { case TypeCode.Boolean: // Use custom ToBool method return(_ToBool(value)); case TypeCode.Byte: return(VBConvert.ToByte(value)); case TypeCode.Char: return(VBConvert.ToChar(value)); case TypeCode.DateTime: return(VBConvert.ToDate(value)); case TypeCode.Decimal: return(VBConvert.ToDecimal(value)); case TypeCode.Double: return(VBConvert.ToDouble(value)); case TypeCode.Int16: return(VBConvert.ToShort(value)); case TypeCode.Int32: return(VBConvert.ToInteger(value)); case TypeCode.Int64: return(VBConvert.ToLong(value)); case TypeCode.SByte: return(VBConvert.ToSByte(value)); case TypeCode.Single: return(VBConvert.ToSingle(value)); case TypeCode.String: return(VBConvert.ToString(value)); case TypeCode.UInt16: return(VBConvert.ToUShort(value)); case TypeCode.UInt32: return(VBConvert.ToUInteger(value)); case TypeCode.UInt64: return(VBConvert.ToULong(value)); } // --------------------------------------------------------------------------- // Fall back to VBConvert.ChangeType // --------------------------------------------------------------------------- return(VBConvert.ChangeType(value, targetType)); } catch (Exception ex) { // Conversion of non-empty value to value type failed. if (ignoreError) { return(defaultValue ?? Activator.CreateInstance(targetType)); } throw new InvalidCastException($"Cannot convert {_FormatValue(value)} to type {targetType.FullName}", ex); } } // end IsValueType else { // Reference types...not much to do here besides check for empty values if (ValueInspection.IsNull(value, options)) { return(null); } try { // Fall back to VBConvert.ChangeType return(VBConvert.ChangeType(value, targetType)); } catch { if (ignoreError) { return(defaultValue ?? null); } throw; } } }