C# decimal.TryParse乐于接受格式错误的数字字符串

C# decimal.TryParse乐于接受格式错误的数字字符串,c#,parsing,C#,Parsing,有没有办法让C#TryParse()函数多一点。。。严格的 现在,如果您传入一个包含数字、正确的十进制和千位分隔符的字符串,即使格式没有意义,它通常也会接受它们,例如:123''345'678 我正在寻找一种方法,如果数字格式不正确,则使TryParse无法成功 我的总部设在苏黎世,如果我这样做: decimal exampleNumber = 1234567.89m; Trace.WriteLine(string.Format("Value {0} gets formatted as: \"{

有没有办法让C#
TryParse()
函数多一点。。。严格的

现在,如果您传入一个包含数字、正确的十进制和千位分隔符的字符串,即使格式没有意义,它通常也会接受它们,例如:
123''345'678

我正在寻找一种方法,如果数字格式不正确,则使
TryParse
无法成功

我的总部设在苏黎世,如果我这样做:

decimal exampleNumber = 1234567.89m;
Trace.WriteLine(string.Format("Value {0} gets formatted as: \"{1:N}\"", exampleNumber, exampleNumber));
…然后,通过我的区域设置,我得到了这个

Value 1234567.89 gets formatted as: "1'234'567.89"
你可以看到,对于我的区域,小数点字符是一个句号,千位分隔符是一个撇号

现在,让我们创建一个简单的函数来测试
字符串
是否可以解析为
十进制

private void ParseTest(string str)
{
    decimal val = 0;
    if (decimal.TryParse(str, out val))
        Trace.WriteLine(string.Format("Parsed \"{0}\" as {1}", str, val));
    else
        Trace.WriteLine(string.Format("Couldn't parse: \"{0}\"", str));
}
好的,让我们用几个字符串调用这个函数

您认为此函数可以成功解析以下哪些字符串

以下是我得到的结果:

ParseTest("123345.67");         //  1. Parsed "123345.67" as 123345.67
ParseTest("123'345.67");        //  2. Parsed "123'345.67" as 123345.67
ParseTest("123'345'6.78");      //  3. Parsed "123'345'6.78" as 1233456.78
ParseTest("1''23'345'678");     //  4. Parsed "1''23'345'678" as 123345678
ParseTest("'1''23'345'678");    //  5. Couldn't parse: "'1''23'345'678"
ParseTest("123''345'678");      //  6. Parsed "123''345'678" as 123345678
ParseTest("123'4'5'6.7.89");    //  7. Couldn't parse: "123'4'5'6.7.89"
ParseTest("'12'3'45'678");      //  8. Couldn't parse: "'12'3'45'678"
我想你能理解我的观点

对我来说,只有前两个字符串应该被成功解析。其他的应该都失败了,因为它们在1000个分隔符后没有3位数字,或者两个撇号在一起

即使我将
ParseTest
更改为更具体一点,结果也完全相同。(例如,它愉快地接受“
123”“345'678
”作为有效的十进制数。)

那么,有没有一种简单的方法不允许格式错误的字符串被
TryParse
接受

更新

谢谢你的建议

也许我应该澄清一下:我想要的是前两个字符串是有效的,但第三个字符串被拒绝

ParseTest("123345.67");
ParseTest("123'456.67");
ParseTest("12'345'6.7");
当然,一定有办法使用“
NumberStyles.AllowThousands
”,这样它就可以选择性地允许使用千个分隔符,但要确保数字格式有意义吗

现在,如果我用这个:

if (decimal.TryParse(str, styles, CultureInfo.CurrentCulture, out val))
if (decimal.TryParse(str, styles, CultureInfo.InvariantCulture, out val))
我得到以下结果:

Parsed "123345.67" as 123345.67
Parsed "123'456.67" as 123456.67
Parsed "12'345'6.7" as 123456.7
Parsed "123345.67" as 123345.67
Couldn't parse: "123'456.67"
Couldn't parse: "12'345'6.7"
如果我用这个:

if (decimal.TryParse(str, styles, CultureInfo.CurrentCulture, out val))
if (decimal.TryParse(str, styles, CultureInfo.InvariantCulture, out val))
我得到以下结果:

Parsed "123345.67" as 123345.67
Parsed "123'456.67" as 123456.67
Parsed "12'345'6.7" as 123456.7
Parsed "123345.67" as 123345.67
Couldn't parse: "123'456.67"
Couldn't parse: "12'345'6.7"

这是我的问题。。。无论CultureInfo设置如何,都应拒绝第三个字符串,并接受前两个字符串。

这是因为解析只是跳过了
NumberFormatInfo.NumberGroupSeparator
字符串,而完全忽略了
NumberFormatInfo.NumberGroupSizes
属性。但是,您可以实现这样的验证:

static bool ValidateNumberGroups(string value, CultureInfo culture)
{
    string[] parts = value.Split(new string[] { culture.NumberFormat.NumberGroupSeparator }, StringSplitOptions.None);
    foreach (string part in parts)
    {
        int length = part.Length;
        if (culture.NumberFormat.NumberGroupSizes.Contains(length) == false)
        {
            return false;
        }
    }

    return true;
}
它仍然不完全完美,因为MSDN:

数组的第一个元素定义紧靠NumberDecimalSeparator左侧的最低有效数字组中的元素数。每个后续元素指前一组左边的下一个有效数字组。如果数组的最后一个元素不是0,则将根据数组的最后一个元素对其余数字进行分组。如果最后一个元素为0,则剩余的数字不分组

例如,如果数组包含{3,4,5},则数字分组类似于“5555555444333.00”。如果数组包含{3,4,0},则数字分组类似于“5555555 4444333.00”


但是您现在可以看到要点。

根据当前区域性判断其格式是否正确的最简单方法是将格式化后的结果数字与原始字符串进行比较

//input = "123,456.56" -- true
//input = "123,4,56.56" -- false
//input = "123456.56" -- true
//input = "123,,456.56" -- false
string input = "123456.56";
decimal value;

if(!decimal.TryParse(input, out value))
{
    return false;
}

return (value.ToString("N") == input || value.ToString() == input);
对于完全省略千位分隔符的输入和指定正确千位分隔符的输入,这将成功


如果您需要它接受一个小数位数范围,那么您需要获取小数分隔符后的字符数,并将其附加到“N”格式字符串中。

将所有有用的建议放在一起,下面是我最后使用的内容

它并不完美,但对于我的公司应用程序来说,它至少拒绝了“看起来不正确”的数字字符串

在我展示代码之前,下面是我的
TryParseExact
函数将接受的内容与常规
decimal.TryParse
函数将接受的内容之间的区别:

这是我的密码

我相信有一种更有效的方法可以做到这一点,使用
regex
或其他什么,但这足以满足我的需要,我希望它能帮助其他开发人员:

    public static bool TryParseExact(string str, out decimal result)
    {
        //  The regular decimal.TryParse() is a bit rubbish.  It'll happily accept strings which don't make sense, such as:
        //      123'345'6.78
        //      1''23'345'678
        //      123''345'678
        //
        //  This function does the same as TryParse(), but checks whether the number "makes sense", ie:
        //      - has exactly zero or one "decimal point" characters
        //      - if the string has thousand-separators, then are there exactly three digits inbetween them 
        // 
        //  Assumptions: if we're using thousand-separators, then there'll be just one "NumberGroupSizes" value.
        //
        //  Returns True if this is a valid number
        //          False if this isn't a valid number
        // 
        result = 0;

        if (str == null || string.IsNullOrWhiteSpace(str)) 
            return false;

        //  First, let's see if TryParse itself falls over, trying to parse the string.
        decimal val = 0;
        if (!decimal.TryParse(str, out val))
        {
            //  If the numeric string contains any letters, foreign characters, etc, the function will abort here.
            return false;
        }

        //  Note: we'll ONLY return TryParse's result *if* the rest of the validation succeeds.

        CultureInfo culture = CultureInfo.CurrentCulture;
        int[] expectedDigitLengths = culture.NumberFormat.NumberGroupSizes;         //  Usually a 1-element array:  { 3 }
        string decimalPoint = culture.NumberFormat.NumberDecimalSeparator;          //  Usually full-stop, but perhaps a comma in France.
        string thousands = culture.NumberFormat.NumberGroupSeparator;               //  Usually a comma, but can be apostrophe in European locations.

        int numberOfDecimalPoints = CountOccurrences(str, decimalPoint);
        if (numberOfDecimalPoints != 0 && numberOfDecimalPoints != 1)
        {
            //  You're only allowed either ONE or ZERO decimal point characters.  No more!
            return false;
        }

        int numberOfThousandDelimiters = CountOccurrences(str, thousands);
        if (numberOfThousandDelimiters == 0)
        {
            result = val;
            return true;
        }

        //  Okay, so this numeric-string DOES contain 1 or more thousand-seperator characters.
        //  Let's do some checks on the integer part of this numeric string  (eg "12,345,67.890" -> "12,345,67")
        if (numberOfDecimalPoints == 1)
        {
            int inx = str.IndexOf(decimalPoint);
            str = str.Substring(0, inx);
        }

        //  Split up our number-string into sections: "12,345,67" -> [ "12", "345", "67" ]
        string[] parts = str.Split(new string[] { thousands }, StringSplitOptions.None);

        if (parts.Length < 2)
        {
            //  If we're using thousand-separators, then we must have at least two parts (eg "1,234" contains two parts: "1" and "234")
            return false;
        }

        //  Note: the first section is allowed to be upto 3-chars long  (eg for "12,345,678", the "12" is perfectly valid)
        if (parts[0].Length == 0 || parts[0].Length > expectedDigitLengths[0])
        {
            //  This should catch errors like:
            //      ",234"
            //      "1234,567"
            //      "12345678,901"
            return false;
        }

        //  ... all subsequent sections MUST be 3-characters in length
        foreach (string oneSection in parts.Skip(1))
        {
            if (oneSection.Length != expectedDigitLengths[0])
                return false;
        }

        result = val;
        return true;
    }

    public static int CountOccurrences(string str, string chr)
    {
        //  How many times does a particular string appear in a string ?
        //
        int count = str.Length - str.Replace(chr, "").Length;
        return count;
    }
Excel是否在该值以上抱怨,或尝试将其存储为文本?不,它也乐于接受这一有效数字,并将其粘贴为“
1234567.89


不管怎样,工作完成了。。感谢所有人的帮助和建议。

您需要接受有效的千位分隔符吗?如果没有,就不要使用AllowThousands…看起来您需要单独的正则表达式验证。老实说,我很惊讶,即使在
123''345'678
示例中为相邻的数千个分隔符指定了正确的属性,它也能被成功解析。但在这种情况下,您可能只需要将
numbergroupsize
属性指定为
{3,3,0}
。我不知道。所以你们的文化是
de CH
,不是吗?然后我们可以通过预加
System.Threading.Thread.CurrentThread.CurrentCulture=new CultureInfo(“de CH”)@PaulKienitz:想到正则表达式就是为了这个目的,我不寒而栗。正是我想要的。很抱歉在这个问题上浪费了大家的时间,但我只是想,在TryParse存在这么多年之后,一定有办法让它识别格式不正确的字符串。我不是第一个寻找“常识”解析函数的人。。。!我也这么想。但是像
“12'345.666”
“12'345.6”
这样的字符串呢。他们觉得自己很无辜,但由于句点
后的小数位数不完全是两个,因此您的解决方案会拒绝他们。这可以用
.ToString(“#,0.#######################################################35