2015-10-13 20 views
10

C#TryParse()の機能をもう少し厳しくする方法はありますか?decimal.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" 

ので、私の地域では、小数点以下の文字は完全停止し、1000個の区切り文字はアポストロフィであることがわかります。

それでは、stringdecimalに解析することが可能かどうかをテストするための簡単な関数を作成してみましょう:

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" 

以下は私が得た結果です。

私にとって、最初の2つの文字列だけが正常に解析されているはずです。他の人は、1000個の区切り文字の後に3桁の数字がないか、または2つのアポストロフィを一緒に使用しているので、すべて失敗しているはずです。

ParseTestを少し具体的に変更しても、結果はまったく同じです。 (例えば、それは喜んで「123''345'678」は有効な小数で受け付けます。)

private void ParseTest(string str) 
{ 
    decimal val = 0; 
    var styles = (NumberStyles.AllowDecimalPoint | NumberStyles.AllowThousands); 

    if (decimal.TryParse(str, styles, CultureInfo.CurrentCulture, out val)) 
     Trace.WriteLine(string.Format("Parsed \"{0}\" as {1}", str, val)); 
    else 
     Trace.WriteLine(string.Format("Couldn't parse: \"{0}\"", str)); 
} 

をので、不正な形式の文字列がTryParseに受け入れられることはできませへの簡単な方法がありますか?

更新提案のすべてのための

感謝。

おそらく私は明らかにする必要があります:私が探しているものは、これらの文字列の最初の2つは有効ですが、3番目は拒否されます。

ParseTest("123345.67"); 
ParseTest("123'456.67"); 
ParseTest("12'345'6.7"); 

は、確かにそれは、必要に応じて千区切りを許可するが、数の書式メイクセンスを行い確認することができますので、「NumberStyles.AllowThousands」を使用する方法が必要?私が使用している場合

は今のところ、この:

Parsed "123345.67" as 123345.67 
Parsed "123'456.67" as 123456.67 
Parsed "12'345'6.7" as 123456.7 

そして、私が使用している場合は、この:

if (decimal.TryParse(str, styles, CultureInfo.InvariantCulture, out val)) 

私はこれらの結果を得る:

if (decimal.TryParse(str, styles, CultureInfo.CurrentCulture, out val)) 

は、私はこれらの結果を得る

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

これは私の問題です... CultureInfoの設定に関係なく、その3番目の文字列は拒否され、最初の2つは受け入れられます。

+2

*有効な*桁区切り記号を受け入れる必要がありますか?そうでない場合は、AllowThousandsを使用しないでください... –

+0

別の正規表現検証が必要なようです。 –

+0

正直なところ、隣り合っていることに驚いています。セパレータは '123''3445'678'の例では適切なプロパティを割り当てても正常に解析されます。しかし、この場合は 'NumberGroupSizes'プロパティを' {3、3、0} 'にするだけでよいでしょう。知りません。 –

答えて

1

構文解析がNumberFormatInfo.NumberGroupSeparator文字列をスキップし、NumberFormatInfo.NumberGroupSizesプロパティを完全に無視するためです。 MSDN saysとして、それはまだ完全に完璧ではない

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; 
} 

:しかし、あなたは、このような検証を実装することができ、配列の最初の要素はの最下位グループ内の要素の数を定義し

直ちにNumberDecimalSeparatorの左側に数字が表示されます。後続の各要素は、前のグループの左にある次の重要な桁のグループを参照します。配列の最後の要素が0でない場合、残りの桁は配列の最後の要素に基づいてグループ化されます。最後の要素が0の場合、残りの桁はグループ化されません。

たとえば、配列に{3、4,5}が含まれている場合、数字は「55,55555,55555,55555,4444,333.00」のようにグループ化されます。配列に{3、4、0}が含まれている場合、数字は「55555555555555555,4444,333.00」のようにグループ化されます。

しかし、今すぐポイントを見ることができます。

2

現在のカルチャに基づいて正しくフォーマットされているかどうかを確認する最も簡単な方法は、フォーマット後の数値を元の文字列と比較することです。

//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」形式の文字列に追加する必要があります。

+1

Beautiful。まさに私が探していたもの。誰もがこの問題に時間を無駄にしていたことを謝罪しましたが、TryParseが何年も過ぎた後、ひどくフォーマットされた文字列を認識できる方法が必要です。私は "常識"解析機能を探している最初の人にはなりません...! –

+0

私は同じ行に沿って考えました。しかし、 '' 12'345.666 ''や' '12'345.6" 'のような文字列はどうでしょうか?彼らは無邪気だと感じますが、 '.'の後の小数点以下の桁数はちょうど2ではないので、あなたの解で拒否されます。これは '.ToString("#、0。################### ")'または同様の醜いもので修正されるかもしれません。 –

+0

私はすでに答えの終わりに小数点以下の長さを変えた解を含んでいました。 N0、N1、N8などは有効な書式文字列です – ndonohoe

1

すべての有用な提案をここにまとめると、ここで私は使い終わったのです。

これは完璧ではありませんが、私の企業向けアプリケーションでは、少なくとも「見栄えが悪い」数値文字列を拒否します。

私は私のコードを提示する前に、ここに私のTryParseExact機能が受け入れる何の違いはありますし、どのような定期的なdecimal.TryParseは受け入れる:

enter image description here

そしてここでは、私のコードです。

私はこれのいくつかを行うためのより効率的な方法が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; 
    } 

ところで、I Excelで上記の表の画像を作成し、それがExcelにこのような値を貼り付けるには、実際には難しいことに気づい:

1'234567.89 

んExcelは、この値を超えて文句を言う、またはテキストとして保存してみては?いいえ、それは喜んで有効な数字としてこれを受け入れ、それを "1234567.89"として貼り付けます。

とにかく、仕事をしてくれてありがとう。助けを借りて皆様に感謝します&提案。

+0

Regexは実際にはそれを約1〜2行に戻します;-)、とにかく、あなたのソリューションを投稿するために1つ – ArieKanarie

関連する問題