web-dev-qa-db-ja.com

C#Decimal.Parseの問題とコンマ

これが私の問題です(en-USの場合):

Decimal.Parse("1,2,3,4")は、InvalidFormatExceptionをスローする代わりに、1234を返します。

ほとんどのWindowsアプリケーション(Excel en-US)は、千の区切り記号を削除せず、その値を10進数とは見なしません。同じ問題が他の言語でも発生します(ただし、文字は異なります)。

この問題を解決する他の10進解析ライブラリはありますか?

ありがとう!

21
Eduardo Scoz

通貨を手動で確認するためのコードを書く必要がありました。個人的には、すべてのグローバリゼーション関連のものが組み込まれていることに誇りを持っているフレームワークにとって、.NETにはこれを処理するものが何もないのは驚くべきことです。

私の解決策は以下の通りです。フレームワーク内のすべてのロケールで機能します。ただし、Orionが以下で指摘しているように、負の数はサポートしていません。皆さんはどう思いますか?

    public static bool TryParseCurrency(string value, out decimal result)
    {
        result = 0;
        const int maxCount = 100;
        if (String.IsNullOrEmpty(value))
            return false;

        const string decimalNumberPattern = @"^\-?[0-9]{{1,{4}}}(\{0}[0-9]{{{2}}})*(\{0}[0-9]{{{3}}})*(\{1}[0-9]+)*$";

        NumberFormatInfo format = CultureInfo.CurrentCulture.NumberFormat;

        int secondaryGroupSize = format.CurrencyGroupSizes.Length > 1
                ? format.CurrencyGroupSizes[1]
                : format.CurrencyGroupSizes[0];

        var r = new Regex(String.Format(decimalNumberPattern
                                       , format.CurrencyGroupSeparator==" " ? "s" : format.CurrencyGroupSeparator
                                       , format.CurrencyDecimalSeparator
                                       , secondaryGroupSize
                                       , format.CurrencyGroupSizes[0]
                                       , maxCount), RegexOptions.Compiled | RegexOptions.CultureInvariant);
        return !r.IsMatch(value.Trim()) ? false : Decimal.TryParse(value, NumberStyles.Any, CultureInfo.CurrentCulture, out result);
    }

そして、これが機能していることを示す1つのテストです(nUnit):

    [Test]
    public void TestCurrencyStrictParsingInAllLocales()
    {
        var originalCulture = CultureInfo.CurrentCulture;
        var cultures = CultureInfo.GetCultures(CultureTypes.SpecificCultures);
        const decimal originalNumber = 12345678.98m;
        foreach(var culture in cultures)
        {
            var stringValue = originalNumber.ToCurrencyWithoutSymbolFormat();
            decimal resultNumber = 0;
            Assert.IsTrue(DecimalUtils.TryParseCurrency(stringValue, out resultNumber));
            Assert.AreEqual(originalNumber, resultNumber);
        }
        System.Threading.Thread.CurrentThread.CurrentCulture = originalCulture;

    }
10
Eduardo Scoz

Decimal.ParseNumberStyles.Number)で使用されるデフォルトのNumberStyles値にはNumberStyles.AllowThousandsが含まれているため、数千を許可しています。

数千の区切り文字を禁止する場合は、次のようにそのフラグを削除できます。

Decimal.Parse("1,2,3,4", NumberStyles.Number ^ NumberStyles.AllowThousands)

(上記のコードはInvalidFormatExceptionをスローしますが、これはあなたが望むものですよね?)

14
Orion Edwards

これは、2段階のプロセスで実行できる場合があります。まず、_CultureInfo.CurrentCulture.NumberFormat.NumberGroupSeparator_および_CultureInfo.CurrentCulture.NumberFormat.NumberGroupSizes_の情報を使用して千単位の区切り文字を確認し、渡されない場合は例外をスローしてから、その数値をDecimal.Parse()に渡します。

1
Orion Adrian

これは、Microsoftが解決できない一般的な問題です。だから、なぜ1,2,3.00(たとえば英語の文化)が有効なのかわかりません!グループサイズを調べ、テストに合格しなかった場合はfalse /例外(double.parseの失敗など)を返すアルゴリズムを作成する必要があります。組み込みのバリデーターが数千を受け入れないmvcアプリケーションでも同様の問題が発生しました。そのため、double/decimal/float.parseを使用してカスタムで上書きしましたが、グループサイズを検証するロジックを追加しました。

私のソリューションを読みたい場合(これは私のmvcカスタムバリデーターに使用されますが、より良いdouble/decimal/float.parse汎用バリデーターを持つために使用できます)ここに移動します https://stackoverflow.com/a/41916721/3930528

0
user3930528