Convert.ChangeType 十进制不接受“1E-5”科学记数法,如何解决?

Convert.ChangeType doesn't accept "1E-5" scientific notation for Decimal, how to fix?

MRE:https://dotnetfiddle.net/M7WeMH

我有这个通用实用程序:

    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: Input string was not in a correct format.
at System.Number.ThrowOverflowOrFormatException(ParsingStatus status, TypeCode type) at System.Number.ParseDecimal(ReadOnlySpan`1 value, NumberStyles styles, NumberFormatInfo info) at System.Convert.ToDecimal(String value, IFormatProvider provider) at System.String.System.IConvertible.ToDecimal(IFormatProvider provider) at System.Convert.ChangeType(Object value, Type conversionType, IFormatProvider provider) at System.Convert.ChangeType(Object value, Type conversionType) at Program.readValue[T](String value)
at Program.Main()

System.FormatException: Input string was not in a correct format.
at System.Number.ThrowOverflowOrFormatException(ParsingStatus status, TypeCode type) at System.Number.ParseDecimal(ReadOnlySpan`1 value, NumberStyles styles, NumberFormatInfo info) at System.Decimal.Parse(String s) at Program.Main()

这个问题解释了如何修复它 for decimal.Parse (Parse a Number from Exponential Notation) 但是我这个通用方法用于从许多地方的 CSV 文件...是否有等效的修复来避免大量代码重构?

这很难看,但这里有一个如何完成特定类型行为的示例:

static T readValue<T>(string value) where T : struct
{
    var type = typeof(T);
    if (type == typeof(decimal))
    {
        // Put this return statement in a block that verifies the content of the string is scientific notation.
        return (T)Convert.ChangeType(decimal.Parse(value, NumberStyles.Float), typeof(T));
    }
    return (T)Convert.ChangeType(value, typeof(T));
}

原因是因为Decimal.Parse and Decimal.TryParse使用的默认样式是这样的

[ws][sign][digits,]digits[.fractional-digits][ws]

不允许指数。如果您阅读 Parse's documentation,您会看到完整的数字格式是

[ws][$][sign][digits,]digits[.fractional-digits][e[sign]digits][ws]

哪里

e: The 'e' or 'E' character, which indicates that the value is represented in exponential notation. The s parameter can represent a number in exponential notation if style includes the NumberStyles.AllowExponent flag.

这意味着您需要像上面那样使用 NumberStyles.Float(包括 NumberStyles.AllowExponent)来使其工作。一些简单的演示:

var style = System.Globalization.NumberStyles.Float;
var culture = CultureInfo.GetCultureInfo("en-US");
string s = "1E-5";
var i = decimal.Parse(s, style);
decimal dec = 0;
decimal.TryParse(s, style, culture, out dec);