ワテの場合、C#を長年やっていても、文字列を数字に変換する関数(メソッド)の使い方でよく間違える。
具体的には文字列を整数に変換する関数は以下の二種類がある。
Int32.Parse(string s)、int.Parse(string s)など Convert.ToInt32(string s)、Convert.ToInt16(string s)など
つまりなんちゃらParseメソッドとConvertToなんちゃらメソッドの二種類だ。
さらに、Parse系には、事前に変換可能かどうかチェックする手段も用意されている。
int result; bool isParsed = int.TryParse(string s, out result);
このなんちゃらTry.Parseメソッドを実行すると、変換出来る場合には、resultに変換結果も帰って来るのだ。
なので、もしParse系で変換作業を行うなら int.Parse() を使うよりも Try.Parse() を使うほうが良いかも知れない。
で、問題は、Parse系メソッドとConvert系メソッドでnullデータの扱い方で挙動が異なるのだ。
その辺りでいつも混乱するのでこの際、自分の備忘録をかねてParseとConvertでの文字列数値変換の挙動を完全に調査してみた。
結論としては、ワテの場合には従来はその時の気分でParseやConvertを適当に使っていたが、今後はParse系に一本化する予定だ。Convert系は使わない事にする。
その辺りの理由も紹介した。
では本題に入ろう。
ParseとConvertで何が違うのか?
簡単なコンソールプログラムを作成して、
- int.TryParse() の動作
- double.TryParse() 動作
- int.Parse() と Convert.ToInt32() の動作の違い
- double.Parse() と Convert.ToDouble() の動作の違い
の四つの実験をした。
文字列=>数値変換実験に使う文字列データ
以下に示す文字列データを使って変換実験を行った。
static List<string> strLst = new List<string> { "10", "--12345", "12345", "123 45", "123.45", "123.45E2", "123.45e-02", "012345", "0.12345", null, "", "先頭12345", "先頭12345末尾", "12345末尾", };
bool isParsed = int.TryParse(str_i, out result); の実験
以下に示すC#プログラムを実行してみた。
ちょっとややこしそうに見えるかもしれないが、やっている事は、単に上に示した文字列をforループで順番に一個ずつ int.TryParse() しているだけだ。
static void Test_1a_intTry() { Console.WriteLine("===== {0} in =====", mb.GetCurrentMethod()); for (int i = 0; i < strLst.Count; i++) { var str_i = strLst[i]; var str_i_DQ = str_i == null ? "null" : "\"" + str_i + "\""; Console.Write("i={0,-3}{1}({2,-13})", i, "int.Parse", str_i_DQ); int result; bool isParsed = int.TryParse(str_i, out result); Console.Write(" isParsed={0,-5} => ", isParsed); if (isParsed) { Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("結果={0}", result); Console.ResetColor(); } else { Console.WriteLine("変換出来ない"); } } Console.WriteLine("===== {0} out =====\n", mb.GetCurrentMethod()); }
その実行結果は以下の通り。
bool isParsed = int.TryParse(str_i, out result); の結果
===== Void Test_1a_intTry() in ===== i=0 int.Parse("10" ) isParsed=True => 結果=10 i=1 int.Parse("--12345" ) isParsed=False => 変換出来ない i=2 int.Parse("12345" ) isParsed=True => 結果=12345 i=3 int.Parse("123 45" ) isParsed=False => 変換出来ない i=4 int.Parse("123.45" ) isParsed=False => 変換出来ない i=5 int.Parse("123.45E2" ) isParsed=False => 変換出来ない i=6 int.Parse("123.45e-02" ) isParsed=False => 変換出来ない i=7 int.Parse("012345" ) isParsed=True => 結果=12345 i=8 int.Parse("0.12345" ) isParsed=False => 変換出来ない i=9 int.Parse(null ) isParsed=False => 変換出来ない i=10 int.Parse("" ) isParsed=False => 変換出来ない i=11 int.Parse("先頭12345" ) isParsed=False => 変換出来ない i=12 int.Parse("先頭12345末尾" ) isParsed=False => 変換出来ない i=13 int.Parse("12345末尾" ) isParsed=False => 変換出来ない ===== Void Test_1a_intTry() out =====
実行結果は上のようになった。
int.TryParse() の挙動で分かった事
- 入力文字列のうち整数に変換出来るものは “12345” か “012345” など。
- 文字列の前後、途中に数値以外の文字、スペースなどがあると変換出来ない。
- “123.45” も “123.45E2” も変換出来ない。気を利かせて整数部分のみ取得するなどはしない。
- nullは変換出来ない。
- 空文字列 “” も変換出来ない。
ちなみに、 “012345” は12345に変換出来たのだが、では先頭にゼロが何個付いていても変換出来るのか気になった。
少なくとも以下の文字列でさえも12345に変換出来た。
"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012345"
一体全体何個までゼロが先頭に付いていても整数に変換可能なのかは未確認だ。
bool isParsed = double.TryParse(str_i, out result); の実験
同じデータを使って、double.TryParse() を実行してみた。
static void Test_1b_doubleTry() { Console.WriteLine("===== {0} in =====", mb.GetCurrentMethod()); for (int i = 0; i < strLst.Count; i++) { var str_i = strLst[i]; var str_i_DQ = str_i == null ? "null" : "\"" + str_i + "\""; Console.Write("i={0,-3}{1}({2,-13})", i, "double.Parse", str_i_DQ); double result; bool isParsed = double.TryParse(str_i, out result); Console.Write(" isParsed={0,-5} => ", isParsed); if (isParsed) { Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("結果={0}", result); Console.ResetColor(); } else { Console.WriteLine("変換出来ない"); } } Console.WriteLine("===== {0} out =====\n", mb.GetCurrentMethod()); }
bool isParsed = double.TryParse(str_i, out result); の結果
実行結果は以下の通り。
===== Void Test_1b_doubleTry() in ===== i=0 double.Parse("10" ) isParsed=True => 結果=10 i=1 double.Parse("--12345" ) isParsed=False => 変換出来ない i=2 double.Parse("12345" ) isParsed=True => 結果=12345 i=3 double.Parse("123 45" ) isParsed=False => 変換出来ない i=4 double.Parse("123.45" ) isParsed=True => 結果=123.45 i=5 double.Parse("123.45E2" ) isParsed=True => 結果=12345 i=6 double.Parse("123.45e-02" ) isParsed=True => 結果=1.2345 i=7 double.Parse("012345" ) isParsed=True => 結果=12345 i=8 double.Parse("0.12345" ) isParsed=True => 結果=0.12345 i=9 double.Parse(null ) isParsed=False => 変換出来ない i=10 double.Parse("" ) isParsed=False => 変換出来ない i=11 double.Parse("先頭12345" ) isParsed=False => 変換出来ない i=12 double.Parse("先頭12345末尾" ) isParsed=False => 変換出来ない i=13 double.Parse("12345末尾" ) isParsed=False => 変換出来ない ===== Void Test_1b_doubleTry() out =====
double.TryParse() の挙動で分かった事
- 整数の文字列 “12345” は12345に変換出来る。
- 少数の文字列 “123.45” は123.45に変換出来る。
- 浮動小数点形式の文字列 “123.45E2” や “123.45e-02” は正しく実数に変換出来る。
- 文字列の前後、途中に数値以外の文字、スペースなどがあると変換出来ない。
- nullは変換出来ない。
- 空文字列 “” も変換出来ない。
と言う事だ。
int.TryParseやdouble.TryParseのここまでのまとめ
- 文字列の前後、途中に数値以外の文字、スペースなどがあると変換出来ない。
- nullは変換出来ない。
- 空文字列 “” も変換出来ない。
次に、int.Parse() と Convert.ToInt32() の挙動の違いを調べた。
int.Parse() と Convert.ToInt32() の挙動の違い
以下のC#コンソールプログラムを実行した。
ややこしそうに見えるが、中身は単純だ。
二つのtry-catchがあるが、ヘンテコなデータを変換するので例外が出る事もあるのでそれをキャッチしている。
前半のtryでは int.Parse()
後半のtryでは Convert.ToInt32() を実行している。
static void Test_2a_int() { Console.WriteLine("===== {0} in =====", mb.GetCurrentMethod()); for (int i = 0; i < strLst.Count; i++) { var str_i = strLst[i]; var str_i_DQ = str_i == null ? "null" : "\"" + str_i + "\""; try { Console.Write("i={0,-3}, int.Parse({1}) ", i, str_i_DQ); var result1 = int.Parse(str_i); Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("=> {0}", result1); Console.ResetColor(); } catch (Exception ex) { Console.WriteLine("=> 例外"); } try { Console.Write("i={0,-3}, Convert.ToInt32({1}) ", i, str_i_DQ); var result2 = Convert.ToInt32(str_i); Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("=> {0}", result2); Console.ResetColor(); } catch (Exception ex) { Console.WriteLine("=> 例外"); } } Console.WriteLine("===== {0} out =====\n", mb.GetCurrentMethod()); }
int.Parse() と Convert.ToInt32() の挙動の違い実行結果
===== Void Test_2a_int() in ===== i=0 , int.Parse("10") => 10 i=0 , Convert.ToInt32("10") => 10 i=1 , int.Parse("--12345") => 例外 i=1 , Convert.ToInt32("--12345") => 例外 i=2 , int.Parse("12345") => 12345 i=2 , Convert.ToInt32("12345") => 12345 i=3 , int.Parse("123 45") => 例外 i=3 , Convert.ToInt32("123 45") => 例外 i=4 , int.Parse("123.45") => 例外 i=4 , Convert.ToInt32("123.45") => 例外 i=5 , int.Parse("123.45E2") => 例外 i=5 , Convert.ToInt32("123.45E2") => 例外 i=6 , int.Parse("123.45e-02") => 例外 i=6 , Convert.ToInt32("123.45e-02") => 例外 i=7 , int.Parse("012345") => 12345 i=7 , Convert.ToInt32("012345") => 12345 i=8 , int.Parse("0.12345") => 例外 i=8 , Convert.ToInt32("0.12345") => 例外 i=9 , int.Parse(null) => 例外 i=9 , Convert.ToInt32(null) => 0 i=10 , int.Parse("") => 例外 i=10 , Convert.ToInt32("") => 例外 i=11 , int.Parse("先頭12345") => 例外 i=11 , Convert.ToInt32("先頭12345") => 例外 i=12 , int.Parse("先頭12345末尾") => 例外 i=12 , Convert.ToInt32("先頭12345末尾") => 例外 i=13 , int.Parse("12345末尾") => 例外 i=13 , Convert.ToInt32("12345末尾") => 例外 ===== Void Test_2a_int() out =====
上の結果をまとめると以下の通り。
int.Parse() と Convert.ToInt32() の挙動の違いまとめ
- 空文字列 int.Parse(“”), Convert.ToInt32(“”) は共に例外になる
- int.Parse(null) は例外になる。
- Convert.ToInt32(null) はゼロになる。
う~ん、ややこしい。
最後に、
double.Parse() と Convert.ToDouble() の挙動の違い
double.Parse() と Convert.ToDouble() の挙動の違いも調べた。
まあ、実行しなくても大体の予想は付くが、念のために実行してみる。
static void Test_2b_double() { Console.WriteLine("===== {0} in =====", mb.GetCurrentMethod()); for (int i = 0; i < strLst.Count; i++) { var str_i = strLst[i]; var str_i_DQ = str_i == null ? "null" : "\"" + str_i + "\""; try { Console.Write("i={0,-3}, double.Parse({1}) ", i, str_i_DQ); var result1 = double.Parse(str_i); Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("=> {0}", result1); Console.ResetColor(); } catch (Exception ex) { Console.WriteLine("=> 例外"); } try { Console.Write("i={0,-3}, Convert.ToDouble({1}) ", i, str_i_DQ); var result2 = Convert.ToDouble(str_i); Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("=> {0}", result2); Console.ResetColor(); } catch (Exception ex) { Console.WriteLine("=> 例外"); } } Console.WriteLine("===== {0} out =====\n", mb.GetCurrentMethod()); }
double.Parse() と Convert.ToDouble() の挙動の違い実行結果
===== Void Test_2b_double() in ===== i=0 , double.Parse("10") => 10 i=0 , Convert.ToDouble("10") => 10 i=1 , double.Parse("--12345") => 例外 i=1 , Convert.ToDouble("--12345") => 例外 i=2 , double.Parse("12345") => 12345 i=2 , Convert.ToDouble("12345") => 12345 i=3 , double.Parse("123 45") => 例外 i=3 , Convert.ToDouble("123 45") => 例外 i=4 , double.Parse("123.45") => 123.45 i=4 , Convert.ToDouble("123.45") => 123.45 i=5 , double.Parse("123.45E2") => 12345 i=5 , Convert.ToDouble("123.45E2") => 12345 i=6 , double.Parse("123.45e-02") => 1.2345 i=6 , Convert.ToDouble("123.45e-02") => 1.2345 i=7 , double.Parse("012345") => 12345 i=7 , Convert.ToDouble("012345") => 12345 i=8 , double.Parse("0.12345") => 0.12345 i=8 , Convert.ToDouble("0.12345") => 0.12345 i=9 , double.Parse(null) => 例外 i=9 , Convert.ToDouble(null) => 0 i=10 , double.Parse("") => 例外 i=10 , Convert.ToDouble("") => 例外 i=11 , double.Parse("先頭12345") => 例外 i=11 , Convert.ToDouble("先頭12345") => 例外 i=12 , double.Parse("先頭12345末尾") => 例外 i=12 , Convert.ToDouble("先頭12345末尾") => 例外 i=13 , double.Parse("12345末尾") => 例外 i=13 , Convert.ToDouble("12345末尾") => 例外 ===== Void Test_2b_double() out ===== 続行するには何かキーを押してください . . .
double.Parse() と Convert.ToDouble() の挙動の違いまとめ
- 空文字列 double.Parse(“”), Convert.double(“”) は共に例外になる
- double.Parse(null) は例外になる。
- Convert.double(null) はゼロになる。
う~ん、要するに null が来た場合の挙動が異なる。
今回の実験に使ったC#の全ソースコード
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using mb = System.Reflection.MethodBase; namespace _2018_02_22_ParseIntとConvertToInt32の違いConsoleApp { static class MyConsoleClass { public const int SWP_NOSIZE = 0x0001; [DllImport("kernel32.dll", ExactSpelling = true)] public static extern IntPtr GetConsoleWindow(); private static IntPtr MyConsole = GetConsoleWindow(); // dllの中のSetWindowsPos()関数を使う [DllImport("user32.dll", EntryPoint = "SetWindowPos")] public static extern IntPtr SetWindowPos( IntPtr hWnd, int hWndInsertAfter, int x, int Y, int cx, int cy, int wFlags); public static void set_pos_size(int xpos, int ypos, int wid, int hei) { // Consoleウインドウの位置とサイズ指定する。バッファーサイズはmaxにした。 SetWindowPos(MyConsole, 0, xpos, ypos, 0, 0, SWP_NOSIZE); Console.SetBufferSize(Console.BufferWidth, 32766);// バッファーサイズを最大化しておく Console.SetWindowSize(wid, hei);//(120, 60); } } class Program { static void Main(string[] args) { //Console.WriteLine("===== {0} in =====", mb.GetCurrentMethod()); MyConsoleClass.set_pos_size(xpos: 100, ypos: 0, wid: 120, hei: 60); Test_1a_intTry(); Test_1b_doubleTry(); //return; Test_2a_int(); Test_2b_double(); //Console.WriteLine("===== {0} out =====", mb.GetCurrentMethod()); } static List<string> strLst = new List<string> { "10", "--12345", "12345", "123 45", "123.45", "123.45E2", "123.45e-02", "012345", "0.12345", null, "", "先頭12345", "先頭12345末尾", "12345末尾", }; static void Test_1a_intTry() { Console.WriteLine("===== {0} in =====", mb.GetCurrentMethod()); for (int i = 0; i < strLst.Count; i++) { var str_i = strLst[i]; var str_i_DQ = str_i == null ? "null" : "\"" + str_i + "\""; Console.Write("i={0,-3}{1}({2,-13})", i, "int.Parse", str_i_DQ); int result; bool isParsed = int.TryParse(str_i, out result); Console.Write(" isParsed={0,-5} => ", isParsed); if (isParsed) { Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("結果={0}", result); Console.ResetColor(); } else { Console.WriteLine("変換出来ない"); } } Console.WriteLine("===== {0} out =====\n", mb.GetCurrentMethod()); } static void Test_1b_doubleTry() { Console.WriteLine("===== {0} in =====", mb.GetCurrentMethod()); for (int i = 0; i < strLst.Count; i++) { var str_i = strLst[i]; var str_i_DQ = str_i == null ? "null" : "\"" + str_i + "\""; Console.Write("i={0,-3}{1}({2,-13})", i, "double.Parse", str_i_DQ); double result; bool isParsed = double.TryParse(str_i, out result); Console.Write(" isParsed={0,-5} => ", isParsed); if (isParsed) { Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("結果={0}", result); Console.ResetColor(); } else { Console.WriteLine("変換出来ない"); } } Console.WriteLine("===== {0} out =====\n", mb.GetCurrentMethod()); } static void Test_2a_int() { Console.WriteLine("===== {0} in =====", mb.GetCurrentMethod()); for (int i = 0; i < strLst.Count; i++) { var str_i = strLst[i]; var str_i_DQ = str_i == null ? "null" : "\"" + str_i + "\""; try { Console.Write("i={0,-3}, int.Parse({1}) ", i, str_i_DQ); var result1 = int.Parse(str_i); Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("=> {0}", result1); Console.ResetColor(); } catch (Exception ex) { Console.WriteLine("=> 例外"); } try { Console.Write("i={0,-3}, Convert.ToInt32({1}) ", i, str_i_DQ); var result2 = Convert.ToInt32(str_i); Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("=> {0}", result2); Console.ResetColor(); } catch (Exception ex) { Console.WriteLine("=> 例外"); } } Console.WriteLine("===== {0} out =====\n", mb.GetCurrentMethod()); } static void Test_2b_double() { Console.WriteLine("===== {0} in =====", mb.GetCurrentMethod()); for (int i = 0; i < strLst.Count; i++) { var str_i = strLst[i]; var str_i_DQ = str_i == null ? "null" : "\"" + str_i + "\""; try { Console.Write("i={0,-3}, double.Parse({1}) ", i, str_i_DQ); var result1 = double.Parse(str_i); Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("=> {0}", result1); Console.ResetColor(); } catch (Exception ex) { Console.WriteLine("=> 例外"); } try { Console.Write("i={0,-3}, Convert.ToDouble({1}) ", i, str_i_DQ); var result2 = Convert.ToDouble(str_i); Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("=> {0}", result2); Console.ResetColor(); } catch (Exception ex) { Console.WriteLine("=> 例外"); } } Console.WriteLine("===== {0} out =====\n", mb.GetCurrentMethod()); } } }
まとめ
当記事では、ワテの鬼門とするC#の文字列数値変換処理に使うParseとConvertに付いて調査した。
今回の調査で、ワテの疑問は完全に解消した。
つまり、以下の事実が判明した。
- Parse, Convertともに、数値に変換出来る文字列は “12345” や “123.45” などの正しい数値を表す文字列のみ。
- 文字列の前後、途中に数値以外の文字、スペースなどが入っていると例外が出る。
- Parse, Convertともに、文字列の先頭に沢山の0(ゼロ)が付いていても数値変換出来る。
- Parse, Convertともに、”0xFFFF” などの形式も例外になる。16進数とは認識出来ない。
- Parse, Convertともに、Doubleへの変換は浮動小数点形式でも可能。
- Parse, Convertともに、空文字列 “” は例外が出る。
- Parseは null も例外になる。
- Convertは null は 0(ゼロ)になる。
ParseとConvertの違いで要点をまとめると以下の通り。
int.Parse(null) => 例外 Convert.ToInt32(null) => 0 int.Parse("") => 例外 Convert.ToInt32("") => 例外
つまり “” 空文字列はいずれも例外になる。
Convert.Toナンチャラ(null) に限り 0 ゼロなのだ。
その理由は知らない。
でもまあ null が勝手にゼロになってしまうのはワテとしては、気になる。
何故かと言うと、もし
null = 0
などと言う等式が成り立つと、もう世の中混乱しまくり。
あるいは、宇宙が破綻するだろう。
と言う事で、今後のワテは文字列を数値変換する場合には、Convert.ToInt32() などのConvertクラスのメソッドの使用は止める事にする。
使うのは int.Parse() 系のみ。
その中でも int.TyrParse() を使えば変換可能判定と同時に、可能なら変換結果も得られるのでこれを使うかな。
まてよ?
でも、よく考えてみたら、ParseにしてもConvertにしても、これらの変換関数を使う場面と言うのは、多くの場合、その文字列には何らかの数字を表す文字列が入っている場合が多いだろう。
例えばページ番号を文字列で取得した場合など。
英数字、漢字、平仮名などがミックスしたランダムな文字列を数字変換する場面に出くわす可能性は少ない。
でも、その文字列が空文字列やnullの場合も有り得る、そんなケースが殆どだろう。
だったら、ParseでもConvertでも例えば、以下の様に事前に空文字列、ヌル文字判定をして、
string str = null; int value1; if (!string.IsNullOrWhiteSpace(str)) { value1 = Convert.ToInt32(str); } else { value1 = -1; // nullや空文字列の場合に与える何らかの値 }
とすれば、良いのかな?
こうすればParseでもConvertでも、文字列数値変換において、文字列が空文字列やnull値の場合には自分の好きな値を変換結果として与える事が出来るし。
あるいは一行に書きたければ、
var value2 = Convert.ToInt32(string.IsNullOrWhiteSpace(str) ? "-1" : str);
とすれば出来なくは無いぞ⁉
などと、ヘンテコな事を考えていると、益々泥沼にはまりそうなので、今後はワテの場合には、素直にint.TryParse() を使う普通のやり方に統一しようと思う。
「Convert.ToInt32(null)がゼロになる」の覚え方
コンバートをゴキブリに塗るとゼロになる(駆除していなくなる)。
それを数式で表すと、
Convert.ToInt32(null) = 0
どう!
Convertでnullの場合の世界初の暗記方法に違いない。
なお、正しくはコンバットはゴキブリに塗るのでは無くて、食べさせるエサであるが。
商品説明
「コンバット お外用 6個」は、外に置いて外から入るゴキブリの侵入を防ぐ殺虫剤 ゴキブリ用です。外でエサを食べたゴキブリに効き、巣に戻って巣のゴキブリにも効きます。誘引範囲は限られているので、遠くにいるゴキブリは呼びません。雨や風に強い容器構造、スリムで目立たないデザイン。医薬部外品。
まあ、そこは気にしない。
コメント