Convert.ChangeType无法解析Decimal的1E-5类科学计数法字符串,如何修复?
最小可复现示例已公开可验证
我有如下通用工具方法:
static T readValue<T>(string value) { return (T)Convert.ChangeType(value, typeof(T)); }
我需要从文件中读取所有应为decimal类型的数值,当文本为科学计数法格式(例如"1E-5")时会抛出异常,这类格式在文件中出现频率较低。进一步测试发现decimal.Parse也存在相同问题:
using System; public class Program { static T readValue<T>(string value) { return (T)Convert.ChangeType(value, typeof(T)); } public static void Main() { try { Console.WriteLine($"{readValue<decimal>("1E-5")}"); } catch(Exception e) { Console.WriteLine(e); } try { Console.WriteLine($"{decimal.Parse("1E-5")}"); } catch(Exception e) { Console.WriteLine(e); } } }
报错信息如下:
System.FormatException: 输入字符串的格式不正确。
在 System.Number.ThrowOverflowOrFormatException(ParsingStatus status, TypeCode type) 位置 System.Number.ParseDecimal(ReadOnlySpan`1 value, NumberStyles styles, NumberFormatInfo info) 位置 System.Convert.ToDecimal(String value, IFormatProvider provider) 位置 System.String.System.IConvertible.ToDecimal(IFormatProvider provider) 位置 System.Convert.ChangeType(Object value, Type conversionType, IFormatProvider provider) 位置 System.Convert.ChangeType(Object value, Type conversionType) 位置 Program.readValue[T](String value)
在 Program.Main()System.FormatException: 输入字符串的格式不正确。
在 System.Number.ThrowOverflowOrFormatException(ParsingStatus status, TypeCode type) 位置 System.Number.ParseDecimal(ReadOnlySpan`1 value, NumberStyles styles, NumberFormatInfo info) 位置 System.Decimal.Parse(String s) 位置 Program.Main()
已经有相关问题说明过decimal.Parse的修复方案,但这个泛型方法被多处调用用于加载各类CSV文件数据,有没有等效的解决方案可以避免大量代码重构?
你只需要修改readValue<T>方法的内部实现,针对decimal类型做单独适配即可,上层所有调用逻辑完全不需要改动,不会带来额外的重构工作量。
默认情况下Convert.ChangeType和无参的decimal.Parse都不会开启科学计数法的解析支持,需要显式指定对应的解析样式:
using System.Globalization; static T readValue<T>(string value) { // 仅对decimal类型做特殊处理,其他类型保持原有逻辑不变 if (typeof(T) == typeof(decimal)) { return (T)(object)decimal.Parse( value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture ); } return (T)Convert.ChangeType(value, typeof(T)); }
补充说明:
NumberStyles.Float已经覆盖了科学计数法、正负号、小数点这些常见数值场景,加上NumberStyles.AllowThousands是为了兼容带千分位分隔符的数值格式,你可以根据自己的文件实际格式调整样式组合。- 如果你的文件使用非通用的区域格式,把
CultureInfo.InvariantCulture替换成对应区域的CultureInfo实例即可。 - 如果后续发现float、double等其他数值类型也有科学计数法解析需求,照着decimal的分支逻辑新增对应处理即可,依然不需要修改上层调用代码。
如果需要更高的兼容性,还可以加一层异常回退逻辑,特殊解析失败时 fallback 到原有的Convert.ChangeType逻辑,避免特殊格式的字符串引发新的问题:
using System.Globalization; static T readValue<T>(string value) { if (typeof(T) == typeof(decimal)) { if (decimal.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var decVal)) { return (T)(object)decVal; } } return (T)Convert.ChangeType(value, typeof(T)); }
内容的提问来源于stack exchange,提问作者Mr. Boy

