C#プログラミングTips

文字列処理に関するヒント

デジタルアドバンテージ
2002/02/21


 今回は文字列について取り上げ、比較的よく用いられるであろういくつかのパターンを簡単なコードとともにまとめてみる。

 C#では、文字列をstring型として扱うことができる。これは実際にはSystem.String型のオブジェクトであり、オブジェクト内部では、文字列はUnicode文字として扱われる。 

文字列はUnicode

 次のプログラム・コードでは、2つの文字列オブジェクトを生成し、その文字列の長さを3つの方法でコンソールに表示している(以下に示したのは、解説に必要なコードの一部分である。プログラム全体はここ(string.cs)からダウンロードできる)。

11: string s1 = "うらにわにはにわにわにはにわにわとりがいる";
12: String s2 = "うらにわには2わにわには2わにわとりがいる";
13:
14: Console.WriteLine("s1の長さ:" + s1.Length);
15: // s1の長さ:21
16: Console.WriteLine(String.Concat("s2の長さ:", s2.Length));
17: // s2の長さ:21
18:
19: Console.WriteLine(
20:   "s1の長さ:{0}\ns2の長さ:{1}", s1.Length, s2.Length);
21: // s1の長さ:21
22: // s2の長さ:21
2つの文字列オブジェクトを生成し、文字列の長さを3つの方法で表示する。

 まず、文字列の型である11行目のstring型と12行目のString型(System.String型)はまったく同等に使用することができる。リストから分かるとおり、文字列オブジェクトの生成は、New演算子を使わずに、ダブルクォートで囲んだ文字列を変数に代入するだけでいい。

 文字列の長さは、オブジェクトのLengthプロパティから取得することができる。ここでs1には、すべて全角文字からなる21文字の文字列を代入し、s2ではそのうちの2文字を半角英数字である「2」に置き換えた文字列を代入している。しかしUnicodeでは半角文字も全角文字も区別なく同じ1文字として扱われるため、文字列s1とs2は同じ長さとなる(おのおのの実行結果は該当コード付近にコメントとしてリストに埋め込んでいる)。

 コンソールへの表示には、“Hello Worldプログラム”でお馴染みのConsole.WriteLineメソッドを使用するが、文字列定数と文字列変数の内容を連結して表示する場合には“+演算子”がお手軽な方法だ(14行目)。これは実質的に、続く16行目のString.Concatメソッドにより文字列を連結しているのと同じである(String.Concatメソッドにより+演算子をオーバーロードしている)。

 表示用の文字列中に、複数の変数の内容を埋め込んで表示したい場合には、フォーマット文字列を使用した方が便利である(19〜20行目)。これは第1パラメータにフォーマット文字列を指定し、展開する変数を第2パラメータ以降に列挙する。フォーマット文字列では、{0}、{1}というふうに、0ベースのインデックスで第2パラメータ以降に列挙した変数の記述位置を指定する。なおこの場合、内部的にはフォーマット文字列を処理するためのString.Formatメソッドが呼び出されている。

インデクサによる文字列へのアクセス

 次は文字列に含まれる各文字を取り出してみる。各文字へのアクセスは、C#のインデクサを利用して実装されており、非常に直感的で簡単に、あたかも文字列が文字の配列であるかのように行うことができる。

26: for (int i = 0; i < s1.Length; i++) {
27:   Console.Write(s1[i]);
28: }
29: Console.WriteLine();
30: // うらにわにはにわにわにはにわにわとりがいる
文字列の各文字を先頭から順に連続して表示する。

 ただしインデクサによるアクセスは読み出しのみで、書き込みはできない。そもそも、ひとたび生成された文字列はその内容を変更することはできない。文字列の内容を変更しているように見えるいくつかの文字列操作メソッドは、すべて別の文字列オブジェクトへの参照を返しているだけである。

文字による文字列の分割

 文字列中に特定の文字があったら、その位置で文字列を分割するというのはよく必要になる処理の1つだ。例えばCSV形式のファイルの各行からデータ部分を抽出する処理などがこれにあたる。StringクラスにはこのためのSplitメソッドが用意されている。

34: char[] delimiter = new char [] {'は'};
35: foreach (string sub in s1.Split(delimiter)) {
36:   Console.WriteLine(sub);
37: }
38: // うらにわに
39: // にわにわに
40: // にわにわとりがいる
Splitメソッドを用い、文字列を“は”の文字で区切って表示する。

 Splitメソッドのパラメータには、char型の配列を指定する(34行目)。ここではひらがな1文字により文字列を分割しているが、例えば、スペースまたはカンマで文字列を区切る場合には、Splitメソッドのパラメータとなる配列は次のようになる。

char[] delimiter = new char [] {' ', ','};

 このSplitメソッドは、分割されてできた複数の文字列を文字列配列として返す。この例では、foreach文によりそれらを順番に表示している(35行目)。

文字列による文字列の分割

 このように、特定の文字による文字列分割の機能はクラス・ライブラリで用意されているが、文字列による文字列の分割は見あたらないためここで作成してみた。次のコードは、文字列“には”により、長い文字列を分割しながら表示している(for文にコードを詰め込んでいて少しトリッキーになっているが)。

44: int head, tail;
45: string sep = "には";
46:
47: for (head = 0 ;
48:       (tail = s1.IndexOf(sep, head)) != -1;
49:         head = tail + sep.Length) {
50:   Console.WriteLine(s1.Substring(head, tail - head));
51: }
52: Console.WriteLine(s1.Substring(head));
53: // うらにわ
54: // にわにわ
55: // にわにわとりがいる
文字列を文字列“には”により分割し、表示する。

 ここでは、Stringクラスに含まれる2つのメソッドを使用している。1つは文字列中に含まれる文字列の最初の出現位置を取得することができるIndexOfメソッドである(48行目)。このメソッドは文字列が見つからなかった場合には-1を返す。また第2パラメータに数値を指定した場合には、その位置より文字列内の検索を開始する。

 もう1つのメソッドは、文字列から特定部分の文字列を取り出すSubstringメソッドである(50、52行目)。第2パラメータを省略した場合には、第1パラメータで指定した位置から最後までの文字列を切り出すが(52行目)。必要なら、第2パラメータで切り出す文字数を指定することができる(50行目)。

Shift-JISコードでの処理

 冒頭で述べたとおり、プログラム内では文字列をUnicode形式で扱えるが、特にWindows環境で使用されるテキスト・ファイルやデータベースなどの外部データでは、いまだにShift-JISコードが主流である。またレガシー・システムとの連携から、半角文字は1バイト、全角文字は2バイトということを意識せざるを得ない場合もありうる。そこで次はShift-JISにまつわるいくつかの処理をまとめてみた。

 まず、文字列をShift-JIS形式と見なし、文字列のバイト数をカウントする方法は次のとおり。

59: Encoding sjis = Encoding.GetEncoding("shift-jis");
60:
61: Console.WriteLine(sjis.GetByteCount(s1)); // 42
62: Console.WriteLine(sjis.GetByteCount(s2)); // 40
Unicode文字列をShift-JIS文字列としてバイト数をカウントする。

 このように、最初にShift-JISをサポートしているEncodingオブジェクトを取得する(59行目)。ここでは“shift-jis”という文字列によってデータ形式を指定しているが、このパラメータには、次のようにコード・ページ番号を指定することもできる(Shift-JISに対応したコード・ページは「932」である)。

Encoding sjis = Encoding.GetEncoding(932);

 こうして得たEncodingオブジェクトでは、処理対象となる文字列がShift-JIS文字列として扱われる。リストにあるとおり、文字列のバイト数を得るにはGetByteCountメソッドを使用する(61〜62行目)。s1およびs2は冒頭のコードで定義している文字列だ。s2の方は2文字が半角になっているので、実行結果はs1より2バイト少なくなる。

 次のコードは、GetBytesメソッドにより文字列をShift-JISでのバイト列に変換し(66行目)、それによって得られたbyte型配列を、GetStringメソッドにより、再びUnicode文字列に戻して表示している例だ(73行目)。

66: byte[] bstr = sjis.GetBytes("2わのにわとり");
67: foreach (byte b in bstr) {
68:   Console.Write("{0:X} ", b);
69: }
70: Console.WriteLine();
71: // 32 82 ED 82 CC 82 C9 82 ED 82 C6 82 E8
72:
73: Console.WriteLine(sjis.GetString(bstr));
74: // 2わのにわとり
Unicode文字列とShift-JIS文字列のバイト列を相互に変換する。

 68行目では、バイト列を16進数で表示するために、WriteLineメソッドの第1パラメータで“{0:X}”というフォーマット文字列を使用している。これは、続く第2パラメータの値を16進数で表示するためのものだ(10〜15は大文字のA〜Fで表現される)。

文字列のフォーマット

 ここで、フォーマット文字列に埋め込んで使用する識別子についてもう少し解説しておこう。これは正確には次のような書式で指定する。

{N[, M][: formatString]} (MとformatStringは省略可)

  • Nには、フォーマットされるパラメータを示す0ベースのインデックスを指定する。
  • Mには、文字列として表示する際の最小幅を指定する。空いている部分には空白文字が使用される。また通常は左詰めだが、負の数値を指定すると右詰めとなる。
  • formatStringでは、フォーマットを指定するための特定の文字列を指定する。例えば、10進数であれば“D”、16進数であれば“X”となる。

 詳しくはリファレンス・マニュアルを参照していただくとして、次のコードでは例として、9から12までの整数値をそれぞれ、「幅4桁」「幅4桁かつ0埋め」「幅4桁で16進数」「幅4桁で16進数、かつ0埋め」で表示している。

78: for (int i = 9; i < 12; i++) {
79:   Console.WriteLine("{0,4}, {0:D4}, {0,4:X}, {0:X4}", i);
80: }
81: //   9, 0009,    9, 0009
82: //  10, 0010,    A, 000A
83: //  11, 0011,    B, 000B
数値を空白埋めや0埋めで、10進数または16進数として幅4桁で表示する。

 先にConsole.WriteLineメソッドのパラメータはString.Formatメソッドに渡されると述べたが、最終的にはパラメータとして列挙されている各オブジェクトのToStringメソッドによってフォーマット文字列が処理される。次の例はConsole.WriteLineメソッドでフォーマット文字列を指定した場合と同じ処理を、ToStringメソッドを用いて記述したものである。

85: Console.WriteLine("{0:x4}", 255);      // 00ff
86: Console.WriteLine(255.ToString("x4")); // 00ff
87:
88: Console.WriteLine("{0:yyyy/MM/dd HH:mm:ss}", DateTime.Now);
89: // 2002/02/19 07:11:21
90: Console.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"));
91: // 2002/02/19 07:11:21
フォーマット文字列は最終的に各オブジェクトのToStringメソッドによって処理される。

verbatim文字列

 最後に、ファイルのパスなどを記述するときに便利な文字列を紹介しておく。これは文字列定数の前に“@(アットマーク)”を付けたもので、「verbatim文字列」と呼ばれる文字列である(「verbatim」は「一字一句変えずに」という意味。“バーベイタム”と発音するらしいが、難しいのでよく英語のまま表記されている)。verbatim文字列の中では、通常の文字列で使用される「\n」、「\t」などのエスケープ・シーケンスが処理されない。このためパスの区切りを示す「\」をそのまま文字列中に記述することできる。

95: string path1 = "\\windows\\system32\\drivers\\etc\\hosts";
96: string path2 = @"\windows\system32\drivers\etc\hosts";
97: Console.WriteLine(path1 == path2); // True
verbatim文字列はパスを表記するのに便利である。

 なお、97行目では2つの異なるインスタンスの比較を行っているが、文字列だけに関してはこれはその内容の比較となり、その結果はここでは“真(True)”となる。

 ちなみに、「NetDictionaryで始めるWebサービス・プログラミング 第4回」の中では、複数行にまたがる長いSQL文を、このverbatim文字列を用いて幾分すっきりと記述している。End of Article

関連記事(Insider.NET内)
NetDictionaryで始めるWebサービス・プログラミング 第4回
 
「C#プログラミングTips」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間