Dr.GUI .NET
Dr.GUI .NET #5

.NET Framework における文字列

May 31, 2002
日本語版最終更新日 2002 年 8 月 29 日

目次

前回のテーマと今回のテーマ
文字列
System.String クラス
C# における文字列
Visual Basic .NET における文字列
標準の .NET Framework API を使用した文字列
StringBuilder
System.Text のエンコーダとデコーダ
正規表現
.NET Framework を試す
今回のテーマと次回のテーマ

自由に話そう! ソースを使えよ、Luke

この記事についてのご意見やご感想は、Dr. GUI .NET のメッセージ ボードへ英語にてお寄せください。

また、Microsoft® ASP.NET アプリケーションとして動作するサンプルのソース コードを調べるか、または新しいウィンドウでソース ファイル (http://msdn.microsoft.com/library/en-us/dnguinet/html/drguinet5testcode.asp?frame=true) をご覧ください。

前回のテーマと今回のテーマ

前回は、すべての .NET クラスの生みの親である System.Object について説明しました。また、メモリ割り当てとガベージ コレクションについても説明したほか、.NET Framework における動的なシステムの動作についても少し触れました。

今回は、.NET ランタイムにおける文字列について説明します。

文字列

String (具体的には System.String) は .NET Framework における文字列の型であり、文字列リテラルも含みます。.NET Framework では、文字列は 0 以上の 16 ビット (2 バイト) Unicode 文字を格納します。

String は不変である

.NET Framework のなじみにくい性質の 1 つに、String オブジェクトが不変であるということがあります。つまり、一度作成すると、値を変更できないということです (ただし、文字列への参照が 1 つのみの場合、その文字列をガベージ コレクションに解放し、元の文字列参照を別の文字列に再割り当てすることは可能です)。

String のメソッドは文字列を操作しているように見えますが、現在の文字列を変更しているわけではなく、代わりに新しい文字列を作成して返します。1 つの文字を変更、挿入、または削除しただけでも、新しい文字列が作成され、元の文字列は破棄されます。

文字列の作成と破棄を繰り返す処理は遅い場合があるので注意が必要ですが、文字列が不変であることには多くの利点があります。所有権、エイリアス、およびスレッドの問題が、不変オブジェクトでははるかにシンプルなものになるためです。たとえば、このような文字列であれば、マルチスレッド プログラミングを安全に実行できます。これは、文字列を変更するようなスレッドによって、他のスレッドに混乱が生じないためです。

StringBuilder による文字列操作

StringBuilder について考えてみます。StringBuilder オブジェクトは文字列ではありませんが、16 ビット Unicode 文字列の操作に使用されます。このオブジェクトにはバッファがあり、通常は文字列で初期化されます (ただし、普通はバッファのサイズより小さい文字列が使用されます)。バッファにある文字は、新しいバッファを作成しなくてもその場で操作できます。つまり、文字の挿入、追加、削除、置換が可能です (もちろん、元のバッファに収まらない長さの文字を挿入すると、新しいバッファが作成されます)。文字の操作が終わった後、StringBuilderToString メソッドを使用して、操作が完了した文字列をバッファから取り出します。ほとんどの場合、StringBuilder.ToString による処理はコピー操作を伴わないので、非常に効率的です。

String と StringBuilder のインデックス作成

どちらの型も Char 型の Unicode 文字を格納するので、指定したインデックスにある Char を 1 つ返す Char と呼ばれるインデクサをサポートしています。C# を使用している場合は、配列インデックスの場合と同じ考え方でこのインデクサにアクセスできます。

String は不変なので、そのインデクサは読み取り専用です。もちろん、StringBuilder インデクサは読み書き可能なので、文字を参照することも変更することもできます。

System.String クラス

System.String はシール クラスです。つまり、そのクラスから継承することはできません (また、そのため、ある種の最適化を行って文字列の処理速度を向上させることができます)。

ほとんどの .NET 言語には文字列のサポート機能が組み込まれます。たとえば、通常は文字列リテラルや、連結などの操作がサポートされています。ただし、構文は言語によって異なります。

C# における文字列

C# における文字列リテラル

C# では、"正規" 文字列リテラルと "逐語的" 文字列リテラルの 2 種類がサポートされています。

正規文字列リテラルは C および C++ の文字列リテラルに似ており、引用符で区切られ、さまざまな制御文字や任意の Unicode 文字を表すエスケープ シーケンスを扱うことができます。途中で改行することはできませんが、+ 演算子を使用すれば、隣接する文字列リテラルをコンパイル時に連結できます。エスケープ シーケンスはすべて円記号 ("\") で始まります。次の表は、エスケープ シーケンスの一覧です。

表 1. C# における文字列リテラルのエスケープ シーケンス

エスケープ シーケンス 説明
\t タブ (Unicode 0x0009)
\r キャリッジ リターン (0x000d)
\n ライン フィード (改行) (0x000a)
\v 垂直タブ (0x000b)
\a アラート (0x0007)
\b バックスペース (0x0008)
\f フォーム フィード (0x000c)
\0 Null (0x0000)
\\ バックスラッシュ (0x005c)
\' 単一引用符 (0x0027)
\" 二重引用符 (0x0022)
\xD さまざまな桁数の 16 進数の文字コード
\uABCD Unicode 文字 0xABCD (先頭の u は小文字でも大文字でもかまいません。A、B、C、D は有効な 16 進数で、0 ~ 9、a ~ f、A ~ F です。)

これらのエスケープ シーケンスはすべて、正規文字列リテラルまたは文字リテラル ('\t' など) で使用できます。また、Unicode のエスケープ シーケンスは識別子で使用できるので、"if (a\U0066b == true)" は "if (afb == true)" と同じです (Unicode の 0x0066 は "f" です)。これにより、その文字がキーボードにない場合や、エディタで使用しているフォントで表示できない場合でも、有効な Unicode 文字を使用して識別子を記述できます。

逐語的文字列リテラルは @" で始まり、対応する引用符で終わります。エスケープ シーケンスはありません。そのため、

@"\\machine\share\path1\path2\file.ext"

という逐語的文字列リテラルは、次の正規文字列リテラルと同じです。

"\\\\machine\\share\\path1\\path2\\file.ext"

ただし、逐語的文字列リテラルの方がずっと簡単で、間違いが少なくて済みます。また、途中で改行することもできます。その場合、次のように引用符の間に空白文字が含まれます。

@"First \t line
   tabbed second line"
// "First \\t line\r\n\ttabbed second line" と同じです。
注 : 2 番目の文字列に "\r" が必要かどうかは、エディタによって異なります。Microsoft Visual Studio® .NET では、行末にキャリッジ リターン/ライン フィードの組み合わせを使用するので、Visual Studio .NET エディタを使用してファイルを作成する際には両方が必要です (他のエディタでは、改行文字 (ASCII コードの 10、またはライン フィード) のみで行を終わらせることができる場合もあります)。

"エスケープ シーケンスがない" という逐語的文字列リテラル規則の唯一の例外は、次のように二重引用符を 2 つ重ねることで逐語的文字列に二重引用符を使用できる点です。

@"This is a ""quote"" from me."
// "This is a \"quote\" from me." と同じです。

C# における文字列操作

C# は次の文字列操作をサポートしています。

  • 個々の文字を読み取る (書き込みは不可) 対象となる文字列のインデックス作成 (例 : s[i])。
  • + 演算子を使用した 2 つの文字列の連結 (例 : s + t)。s または t の一方が文字列でなくてもかまいません。その場合、ToString を呼び出すことで文字列に変換されます。+ 演算子は、String クラスのメンバとしてではなく、C# 言語の一部として実装されます。どちらかのオペランドが Null の場合は (空の文字列 "" とは異なります)、空の文字列に変換されます。この操作は、可能であればコンパイル時に実行されます。
  • 等価と非等価 (例 : s == t および s != t)。これらの演算子は String クラスの一部なので、オーバーロード演算子をサポートする言語から使用できます。演算子の構文ではなく、メンバ名 op_Equality および op_Inequality を使用することで、オーバーロード演算子をサポートしない Microsoft Visual Basic® .NET などの言語からも使用できます。これらの演算子によって String.Equals が呼び出され、2 つの文字列にある文字に対してカルチャを意識した比較ではなく、バイナリとしての比較が実行されます。

連結は、可能であればコンパイル時に実行されます。また、オブジェクトを文字列に連結することもできます。その場合、オブジェクトの ToString メソッドからの戻り値が連結されます。たとえば、次のコードはすべて有効です。

string s0 = " Hello ";
string s1 = s0 + 5;   // " Hello 5"
string s2 = 6.3 + s1;   // "6.3 Hello 5"
string ret = String.Format("s0: {0}\ns1: {1}\ns2: {2}", s0, s1, s2);

また、文字列は不変なので、コンパイラとランタイムでは重複する文字列リテラルが結合され、プログラム内に存在する各リテラルのコピーが 1 つのみになります。

Visual Basic .NET における文字列

Visual Basic .NET における文字列リテラル

Visual Basic .NET の文字列リテラルは非常にシンプルで、二重引用符とそれに続く Unicode 文字のセット、さらにそれを閉じる二重引用符で構成されます。文字列に二重引用符を含めるには、次のように二重引用符を 2 つ重ねます。

"This prints ""Hello, world!""on the screen"

Visual Basic .NET の文字列リテラルや文字リテラルでは、二重引用符を 2 つ重ねる以外は、エスケープ シーケンスのしくみはありません (これは、C# の逐語的文字列リテラルと同じです。ただし、Visual Basic .NET の文字列リテラルに行末文字を含めることはできません。Visual Basic .NET の文字列リテラルは、1 行の中に収めなければなりません)。ライン フィード文字など、Visual Basic .NET プログラムで表せない文字を含める場合は、Chr 関数または ChrW 関数を使用してコードから文字を作成してから、文字列に連結してください。キャリッジ リターン (Chr(13)) を使用すると、画面に 2 つの行が重なって表示されます。これは、キャリッジ リターンでは新しい行に移動しないためです。キャリッジ リターンとライン フィードの両方が必要な場合は、次のように Environment.NewLine プロパティを使用します。

"First line" & Chr(10) & "Second line" ' ライン フィードのみ
"First line" & Environment.NewLine & "Second line" ' キャリッジ リターン/ライン フィード

Visual Basic .NET における文字列操作

Visual Basic .NET には、C# の文字列演算子に加えて、C# にはない文字列演算子があります。Visual Basic .NET の演算子は次のとおりです。

  • + 演算子を使用した String 同士のみの連結 (例 : s + t)。この演算子は、"両方の" オペランドが文字列の場合にのみ文字列を連結します。どちらかのオペランドが文字列ではない場合、Visual Basic .NET は文字列を Double に変換して数値加算しようとします。このため、連結を行う場合は、次に説明する & 演算子を使用することをお勧めします。
  • & 演算子を使用した 2 つのオブジェクトの連結 (例 : s & "There")。& 演算子のオペランドが文字列でない場合は、文字列に変換されます。つまり、x & y のような構文を使用すると、数字 x および y の文字列表現を連結することもできます。& 演算子にはあいまいさがなく、"必ず" 連結を行うので、連結にはこの演算子を使用することをお勧めします。
  • すべての比較演算子の使用 (=、<>、>、<、>=、<=)。コンパイル オプションと Option Compare ステートメント (ある場合) に従って、バイナリまたはテキスト (カルチャを区別) で比較します。Option Compare を使用して比較の種類を設定するか、または使用する比較方法を示すパラメータを渡すことによって、StrComp でも文字列を比較できます。
  • Like 演算子を使用すると、簡易正規表現言語で記述されたパターンと文字列が一致しているかどうかを確認できます。System.Text.RegularExpressions.RegEx でサポートされている正規表現言語にはより多くの機能があるので、その API を使用することもできます。
  • Visual Basic .NET には、配列インデックスの表記を使用して文字列の個々の文字へのアクセスを可能にするようなインデクサはありません。ただし、Chars プロパティを使用すると、文字列から個々の文字を取得でき、また StringBuilder からは個々の文字を取得できるほか設定もできます。

Visual Basic .NET 固有の文字列関数およびステートメント

Visual Basic .NET 言語によって提供される演算子の他に、文字列を操作するための Visual Basic .NET 固有の関数およびステートメントがあります。これらは、標準の .NET Framework ライブラリにはありません。

Visual Basic .NET を使用するプログラマは、Visual Basic 固有のものと .NET Framework 標準のもののどちらを使用するかを選択できます (ただし、分野によっては Framework にはない機能が Visual Basic にある場合もあります)。Dr. GUI は、便利な標準の .NET Framework API の使用を強くお勧めします。この API は、今後他の言語でも使用可能なスキルを身に付ける上で役立ちます。

一方、Visual Basic 固有のものは、従来の Visual Basic コードを Visual Basic .NET に移植する際に非常に便利な場合があります。これは、Visual Basic 固有のものの構文と動作が従来の Visual Basic のものと似ているためです。

部分文字列

すべてを BASIC でプログラミングしたことがある方は、文字列の左、右、および中間から文字を取り出す LeftRigh、および Mid の各関数をご存じのはずです。Mid を代入演算子の左側で使用すると、文字列の中間の文字を変更できることも知られています。

Visual Basic .NET でも、この機能のすべてを BASIC と同じように使用できます。ただし、非常に重要な点が 1 つあります。.NET の他のすべての関数ではインデックスが 0 から始まるのに対して、Mid では先頭の文字に使用するインデックスが 1 から始まるという点です (これは、当初 MidMID$ として BASIC で使用されていたころから、インデックスが 1 から始まっていたためです)。

これは非常に重大な問題なので、新しく作成するコードでは "決して" Mid を使用しないことをお勧めします。もっと正確に言えば、既に機能しているコードを移植する場合にのみ使用するようにしてください。ただし、デバッグにはかなりの覚悟が必要です。1 から始まるカウントと 0 から始まるカウントの間を行ったり来たりしていると、混乱してくるはずです。

String.SubString メソッドには、Left 関数と Mid 関数に代わる優れたオーバーロードがあります。Left の代わりには、str.Substring(0, len) を使用します。Mid のかわりとしては、str.SubString(start, len) を使用します。Substring のインデックスは 0 から始まり、Mid のインデックスは 1 から始まるので、開始点を 1 だけずらすことを忘れないでください。

Right は、開始点を計算しなければならないため、少し複雑です。次のコードはこの例です。

Dim s As String = "Hello, world!"
Dim t1 As String = s.Substring(s.Length - subLen)
      ' 次と同じです。
Dim t2 As String = Right(s, subLen)
Dim ret As String = String.Format("s0: {0}\nt1: {1}\nt2: {2}", s, t1, t2)
ret = ret.Replace("\n", Environment.NewLine)

Dr. GUI は、ここでちょっとしたテクニックを使っています。Visual Basic .NET には改行文字に当たるエスケープ シーケンスがないという事実に対処するため、いったんエスケープ シーケンス ("\n") を含めた後、文字列を返す直前に、すべてのエスケープ シーケンスを 2 文字の Environment.NewLine 文字列 ("\r\n") に置換しています。出力文字列を C# の場合とまったく同じにするには、"\n" を Environment.NewLine ではなく Chr(10) に置換します。

文字列内の文字を置換する Mid ステートメントの場合、置換にはもう少し工夫が必要です。String.Remove を使用して置換する文字を削除し、String.Insert を使用して新しい文字を挿入します。ただし、そのためには 2 つの新しい文字列を作成する必要があります。次のように、変更する文字列の最初の部分文字列から StringBuilder を作成してそれに置換文字列を付加し、さらに元の文字列の末尾をそれに付加すると、より効率的です。

Dim s As String = "123456xxx0"
' SubString のインデックスは 0 から始まります。
Dim sb As StringBuilder = New StringBuilder(s.Substring(0, 6))
sb.Append("789")
sb.Append(s.Substring(9, 1)) ' 0 から数えて 10 番目の文字
s = sb.ToString()
' Mid(s, 7, 3) = "789" と同様です。' インデックスは 1 から始まります。
Dim t As String = "123456xxx0"
Mid(t, 7, 3) = "789"
Dim ret As String = String.Format("s: {0}\nt: {1}", s, t)
ret = ret.Replace("\n", Environment.NewLine)

非常に変わった関数

Visual Basic .NET には、StrConv という非常に変わった関数があります。この関数は、一連のビットによって指定されるさまざまな変換を使用して、文字列を別の文字列に変換します。ファイル システムの規則やカルチャを区別する規則を使用して大文字や小文字に変換します。これには、各単語の先頭文字の小文字から大文字への変換、適切な東アジア言語 (日本語、中国語、韓国語など) のみで意味を持つ (そして、実際にそれらの言語のみで機能する) 全角文字/半角文字の変換やカタカナ/ひらがなの変換 (日本語のみ) などがあります。簡体字中国語と繁体字中国語間の変換もありますが、この変換は十分には機能しないので、使用しないことをお勧めします。

.NET Framework API には、これらに相当する変換はほとんどありません。しかし、Windows の LCMapString API を使用すると、これらの変換が可能になります。Visual Basic 以外の .NET 言語でこれらの変換を実現するには、(Visual Basic と同じように) プラットフォーム API 相互運用機能を使用して LCMapString を呼び出します。

.NET Framework の機能と一致する関数

Visual Basic .NET には、.NET Framework の機能に非常によく似た文字列関数があります。次の表は、Visual Basic .NET の関数とそれに最も近い機能を持つ .NET Framework の関数の一覧です。機能がまったく同じではない場合もあるので、注意してください。また、多くの場合、.NET Framework の関数にはこれらに加えてオーバーロードがあり、さらに多くの機能が提供されています。できる限りこれらの機能を実行できるようにするには、マニュアルを参照する必要があります。次の表で該当するメソッドを探して十分に理解ください。

表 2. Visual Basic 固有の文字列関数とそれに最も近い機能を持つ標準の .NET Framework の関数

Visual Basic .NET の関数 機能 最も近い機能を持つ .NET Framework の関数
StrReverse 文字列の文字の並び順を逆にします。 なし。StringBuilder を使用し、文字の位置を入れ替えて並び順を逆にするループ処理を実行します。
InStr、InStrRev それぞれ部分文字列の最初および最後のインデックスを検索します。 IndexOf、LastIndexOf
LCase、UCase それぞれ小文字および大文字に変換します。 ToLower、ToUpper
Format、FormatCurrency、FormatNumber、FormatPercent カルチャを区別した書式で表現した値を文字列表現に変換します。 obj.ToString または String.Format
Str、Val 数字と文字列間の変換を行います。カルチャは区別しません。 CultureInfo.Invariant を指定した、上記の関数のいずれか
Trim、LTrim、RTrim (スペースのみを対象) 文字列の端のスペースをトリミングします。 String.TrimString.TrimStartString.TrimEnd (スペースだけでなく文字もトリミング可能です。)
Len 文字列の長さを返します。 String.Length
Space、StrDup それぞれスペースおよび他の文字の繰り返しを含む文字列を作成します。 文字およびカウントを受け取る String コンストラクタ
Replace 文字列内の部分文字列を置換します。 String または StringBuilder のいずれかで提供される Replace
Split、Join 指定された区切り文字で文字列を分割して、文字列の配列を作成します。文字列の配列から、指定された文字で区切られた文字列を作成します。 String.Split、String.Join
Filter 別の配列から部分文字列を要素とする文字列配列を作成します。 なし。for-each ループを記述し、動的に拡張可能な ArrayList に文字列を挿入するといった方法が考えられます。
AscW、ChrW Unicode を表す整数と 1 文字の文字列間の変換を行います。 Char 型と整数型間でキャストします。
Asc、Chr コード ページを表す整数と 1 文字の文字列間の変換を行います。 エンコードを使用します (これについては、後で説明します)。
CStr 文字列に変換します。 obj.ToString

標準の .NET Framework API を使用した文字列

String オブジェクトを作成する

どちらの言語でも、new/New ステートメントを使用して String オブジェクトを作成できます。CLI 対応の String コンストラクタのほとんどは、何も受け取らないか、文字や整数、または文字の配列を受け取ります。そのため、文字の配列を扱わない場合は、おそらく別の方法で文字列を作成することになります。文字列を受け取るコンストラクタはありません。ただし、Char と整数カウントを受け取るコンストラクタがあります。これにより、カウントで指定された回数だけ繰り返される文字から成る文字列が返されます。

最も一般的な方法は、次のように文字列リテラルで初期化し、別の文字列から値を割り当てる方法です。

[C#]
string s = "Hello, world!";
string t = s;   // 文字列をコピーするのではなく、s と t で同じ文字列を参照します。
string ret = String.Format("s and t refer to same: {0}",
   Object.ReferenceEquals(s, t));

[Visual Basic .NET]
Dim s as string = "Hello, world"
Dim t as string = s ' 文字列をコピーするのではなく、s と t で同じ文字列を参照します。
Dim ret as string = String.Format("s and t refer to same: {0}", s Is t)

クローンがクローンでなくなるとき

しかし、同じ値を持つ 2 つの独立した文字列が必要な場合はどうすればよいでしょう? メモリの浪費になるため、通常はそのようなことはありません。また、文字列は不変なので、同じ値を持つ 2 つの独立した文字列があってもあまり意味はありません。

したがって、StringIClonable を実装してはいますが、String.Clone は同じ文字列への参照を返すだけで、文字列のクローンを作成することはありません。

ただし、まったく方法がないわけではありません。文字列のコピーがどうしても必要な場合は、静的メソッド Copy を使用することができます。ここでは、参照の等価性を次の 2 つの方法で調べていることに注意してください。1 番目は、Object.ReferenceEquals を呼び出す方法です。2 番目は、C# では等価性をテストする前に object に参照をキャストする方法、Visual Basic では Is 演算子を使用する方法です。

[C#]
string s = "Hello";
string t = (string)s.Clone();   // 文字列をコピーするのではなく、s と t で同じ文字列を参照します。
string u = String.Copy(s); // 文字列をコピーして、s と u は別のオブジェクトを参照します。
string ret = String.Format("s same as t: {0}, s same as u: {1}",
   Object.ReferenceEquals(s, t), (object)s == (object)u);

[Visual Basic .NET]
Dim s as string = "Hello"
Dim t as string = CStr(s.Clone()) ' 文字列をコピーするのではなく、s と t で同じ文字列を参照します。
Dim u as string = String.Copy(s)  ' 文字列をコピーして、s と u は別のオブジェクトを参照します。
Dim ret as string = String.Format("s same as t: {0}, s same as u: {1}", _
       Object.ReferenceEquals(s, t), s Is u)

String.CopyTo メソッドを使用して、文字列の一部または全部を文字配列にコピーすることもできます (文字配列またはその一部から文字列を作成する場合は、パラメータとして文字配列を受け取る文字列コンストラクタを使用します)。

その他のインターフェイス、プロパティ、およびフィールド

既に説明したように、String は ICloneable を実装していますが、その動作は奇妙 (しかし合理的) です。また、String は IComparable も実装しています。そのため、IComparable の CompareTo メソッドを使用して 2 つの文字列を比較できます (これについては、後で詳しく説明します)。String は IConvertable も実装していますが、To... メソッドを使用してすべての組み込みの値型に文字列を変換するには、Convert クラスのメソッドを使用する必要があります。

また、String には興味深いプロパティがあります。文字列の長さ (バイト数ではなく文字数) を調べるには、Length プロパティ (読み取り専用) にアクセスします。また、インデクサ プロパティ Chars (C# インデクサにマップされ、読み取り専用です) を使用すると、文字列の文字に個別にアクセスできます。

[C#]
string s = "Hello, world!";
StringBuilder sb = new StringBuilder(String.Format(
"Length of \"Hello, world!\" is {0}\n", s.Length)); // 13
for (int i = 0; i < s.Length; i++)
   sb.AppendFormat("{0} ", s[i]);

[Visual Basic .NET]
Dim s as string = "Hello, world!"
Dim sb as StringBuilder = new StringBuilder( _
String.Format("Length of ""Hello, world!"" is {0}", s.Length)) ' 13
sb.Append(Environment.NewLine)
Dim i as Integer
For i = 0 to s.Length - 1
   sb.AppendFormat("{0} ", s.Chars(i))
Next

Visual Basic .NET でも、文字列の文字のインデックスは 0 から始まるので、ループを正しく記述するには注意が必要です (C# では、<= 演算子ではなく < 演算子を使用することでこの問題が処理されます)。

空の文字列が格納されている、Empty という興味深い静的フィールドもあります。これを使用すると、言語に依存しない方法で空の文字列 ("") を表すことができます。

文字列を比較するメソッド

2 つの文字列を比較するにはさまざまな方法があります。等価と非等価の比較では、2 つの文字列を参照によって比較するのか (同じオブジェクトを指しているかどうか)、または値によって比較するのか (同じ文字を格納しているかどうか) で大きな違いが生じます。

等価比較と関係比較でも、現在のカルチャの照合順序を使用するのか、それとも文字列にある各文字そのものの値に基づく順序を使用するのかでやはり大きな違いが発生します (大文字小文字を区別して比較するのかといった小さな違いもあります)。既定では、2 つの文字列が動作しているスレッドの現在のカルチャを使用し、大文字小文字は区別します。通常は、これがよく使用される比較方法です。

= 演算子は、String.Equals への呼び出しを生成します。String.Equals は、カルチャを区別し、さらに大文字小文字を区別する比較を行います。C# で参照比較を行う場合は、両方の文字列参照を Object にキャストするか、Object.ReferenceEquals を使用します。Visual Basic .NET では、次のように Visual Basic の Is 演算子を使用するか、Object.ReferenceEquals を使用します。Object.ReferenceEquals は、どちらの言語でも使用できることに注意してください。

[C#]
      string s = "Hello", t = "there";
      bool valueComp = (s == t); // 値比較
      bool refComp1 = ((Object)s == (Object)t); // 参照比較
      bool refComp2 = Object.ReferenceEquals(s, t); // 参照比較
      string ret = String.Format("s == t: {0}, " +
         "(object)s == (object)t: {1}, ObjectRefEq(s, t): {2}",
         valueComp, refComp1, refComp2);

[Visual Basic .NET]
Dim s as string = "Hello"
Dim t as string = "there"
Dim valueComp as Boolean = s = t ' 値比較
Dim refComp1 as Boolean = s Is t ' 参照比較
Dim refComp2 as Boolean  = Object.ReferenceEquals(s, t) ' 参照比較
Dim ret as string = _
String.Format("s = t: {0}, s Is t: {1}, ObRefEq(s, t): {2}", _
   valueComp, refComp1, refComp2)

Visual Basic .NET の等価、非等価、および比較の各演算子 (=、<>、>、<、>=、<=) は、コンパイラ オプションと Option Compare ステートメントに従って、文字の値による順序比較またはカルチャを区別する比較のいずれかを実行します。参照比較を実行するには、Is 演算子を使用します。Visual Basic .NET には、単純なパターン照合を実行する文字列比較用に Like 演算子もあります。

Equals メソッドには、いくつかのバージョンがあります。インスタンス メソッドには 2 つあり、1 つはパラメータとして String を受け取り、もう 1 つは Object (必ず String を参照します) を受け取ります。また、Equals には静的バージョンもあり、2 つの文字列を受け取ります。Object を受け取るメソッド (もともと Object で定義されています) はオーバーライドされ、文字列を受け取るものは、変換が不要なため動作が速くなります。静的バージョンは、null/Nothing が渡されても例外をスローせずに処理できます。

比較を行うためのメソッドには、CompareCompareOrdinal、および CompareTo の 3 つがあります。これらのメソッドはすべて、最初の文字列が 2 番目の文字列より小さい場合は負数を、同じ値の場合は 0 を、大きい場合は正数を返します。

Compare はオーバーロードされる 1 組の静的メソッドです。既定ではカルチャを区別し、さらに大文字小文字を区別する比較を実行します。各オーバーロードは、少なくとも 2 つの比較文字列を受け取ります。また、大文字小文字を区別するかどうかを指定する Boolean と、比較する部分文字列 (文字列全体の比較ではなく) のインデックスおよび長さを指定する Int32 を受け取るオーバーロードもあります。

CompareOrdinal はオーバーロードされる一対の静的メソッドで、2 つの文字列または 2 つの文字列にある部分文字列の値による順序比較を実行します。

CompareTo は、IComparable を実装しているインスタンス メソッドです。これは、現在の文字列と、パラメータとして渡された文字列またはオブジェクトを、カルチャを区別し、さらに大文字小文字を区別して比較します。

文字列の検索 : EndsWith/StartsWith/IndexOf/LastIndexOf/Substring

文字列の内容を調べるためのメソッドも用意されています。EndsWithStartsWith は、それぞれ現在の文字列の末尾または先頭が指定された文字列である場合は true を返し、指定された文字列でない場合は false を返します。

IndexOfLastIndexOf にはそれぞれオーバーロードがあります。これらは、現在の文字列 (またはその部分文字列) の中で、文字列 (または文字配列) が最初または最後に出現する位置を返します。

Substring は、従来の GW-BASIC の MID$ 関数および Visual Basic .NET の Mid 関数と同様です。ただし、文字列のインデックスが Mid 関数と MID$ 関数では 1 から始まるのに対し、Substring では 0 から始まります。これには 2 つのオーバーロードがあり、1 つは、指定されたインデックスから文字列の末尾までの部分文字列から成る新しい文字列を作成して返します。もう 1 つは、指定されたインデックスから指定された長さにある部分文字列から成る新しい文字列を作成して返します。

書式設定

String クラスには、静的な Format メソッド オーバーロードのファミリがあります。これらはすべて、従来の Console.WriteLine 呼び出しで使用してきたものと同じ書式設定機能を備えています。これらのオーバーロードは、書式指定子を残りのパラメータの文字列で置換した新しい文字列を作成して返します。たとえば、次のように使用できます。

[C#]
string ret = String.Format("The value is {0}", 5);
// "値は 5 です" (引用符は含みません。)

[Visual Basic .NET]
Dim ret as string = String.Format("The value is {0}", 5)
' "値は 5 です" (引用符は含みません。)

データの書式設定には他にも方法があり、カルチャの認識に関する多数の問題があります。残念ながら、今回は時間がないので、詳しくは触れません。.NET Framework では通常、書式設定メソッドに CultureInfo.InvariantCulture を渡さない限り、言語に基づく変換と書式設定が実行されます。一方、Visual Basic .NET の組み込みメソッドは、既定ではインバリアント カルチャに設定されていることがほとんどなので、注意が必要です。

解析

変換先となるクラスの Parse メソッドを使用することで、文字列から他の多数の型に変換できます。たとえば、整数に変換するには、Int32.Parse メソッドを使用します。Convert クラスのメソッドを使用するといった他の方法でも変換できます。また、オブジェクトをキャストしたり (C# の場合)、Visual Basic の CType 関数や CStr 関数を使用することもできます。

パディングとトリミング

文字列の左側または右側を空白 (または任意の文字列) で文字を埋めるメソッドとして、PadLeftPadRight があります。また、文字列の先頭、末尾、またはその両方から空白や指定した文字セットを削除するメソッドとして、それぞれ TrimStartTrimEnd、および Trim があります。これらはすべて、新しい文字列を作成して返します。

Insert/Remove/Replace/Concat (挿入、削除、置換、連結)

これらのメソッドは StringBuilder のメソッドと多少似ていますが、String には StringBuilder のようなオーバーロードはほとんどありません。この違いの詳細については、次のセクション「StringBuilder」を参照してください。

Insert は、指定された文字列を指定された位置に挿入した、新しい文字列を作成します。Remove は、指定された数の文字を指定された位置から削除した、新しい文字列を作成します。Replace は、指定された文字または文字列をすべて別の文字または文字列に置換した、新しい文字列を作成して返します。そして、静的メソッド Concat は、渡された文字列やオブジェクトを連結した、新しい文字列を作成して返します。

Split/Join (分割、結合)

SplitJoin は高機能なメソッドです。静的メソッド Join は、指定された文字列を区切り文字として、渡された文字列配列内の文字列の一部または全部を連結した文字列を作成して返します。Split は、指定された区切り文字で文字列を分割し、文字列の配列を作成して返します。

変換

String には、直接アクセスできる変換メソッドはありませんが、Convert クラスに "静的/共有" 変換メソッドがあり、そのほとんどが組み込み型への変換を行い、To... という形式になっています。文字列を組み込み型に変換するメソッドには、ToBooleanToByteToCharToDecimalToDoubleToInt16ToInt32ToInt64ToSByteToSingleToStringToUInt16ToUInt32、および ToUInt64 があります。また、DateTime オブジェクトに変換する ToDateTime メソッドや、バイト配列と Base-64 エンコード文字列間の変換を行う FromBase64String メソッドおよび ToBase64String メソッドもあります (テキスト プロトコルを介したバイナリ データの転送に便利です)。

String には、文字配列に変換する ToCharArray メソッドがあります。また、文字列を大文字または小文字に変換するメソッドとして、ToUpperToLower があります。

Intern/IsInterned

.NET ランタイムは、アプリケーション ドメイン (おおまかに言えば、プロセスのようなもの) 内に文字列リテラルのプールを維持しています。プログラムなどのアセンブリをアプリケーション ドメインに読み込むと、アセンブリの文字列リテラルをアプリケーション ドメインの文字列リテラル プールに結合することで、文字列リテラルの重複を回避します (文字列は不変なので、この処理はプログラムの結果を変えることなく実行できます)。すべての文字列リテラルが内部化されるため、各文字列リテラルのコピーが 1 つしか存在せず、文字列リテラルの参照比較を正しく実行できます。

しかし、String メソッドや StringBuilder など、新しい文字列を作成する手段を使用して自分で作成した文字列は、(リテラル プールに同じ値の文字列があるとすると) リテラル プールにある同じ値の文字列とは異なる文字列になるので、参照比較は失敗します。参照比較で比較されるのはアドレスのみであり、値比較より "はるかに" 高速であるため、これが大きな問題になる可能性があります (したがって、高速な実行が必要なコードでは、動作することが確実な場合のみ参照比較を使用します)。

自分で作成した文字列をリテラル プールに追加するには、静的メソッド Intern を使用します。Intern は、指定された文字列がリテラル プールに存在しない場合はその文字列を追加し、リテラル プール文字列への参照を返します。次のコードはこの例です。

[C#]
string s2 = new StringBuilder().Append("Foo").Append("Bar").ToString();
string s3 =String.Intern(s2);   // リテラル プールへの参照を返します。
string s = "FooBar";            // 既にリテラル プールに存在します。
StringBuilder sb = new StringBuilder();
sb.Append(Object.ReferenceEquals(s2, s));   // false : 異なる文字列
sb.Append(", ");
sb.Append(Object.ReferenceEquals(s3, s));   // true : 同じ文字列

[Visual Basic .NET]
Dim s2 as string = New _
     StringBuilder().Append("Foo").Append("Bar").ToString()
Dim s3 as string = String.Intern(s2) ' リテラル プールへの参照を返します。
Dim s as string = "FooBar"  ' 既にリテラル プールに存在します。
Dim sb as new StringBuilder()
sb.Append(s2 Is s) ' false : 異なる文字列
sb.Append(", ")
sb.Append(s3 Is s) ' true : 同じ文字列

また、静的な IsInterned メソッドを使用して、文字列が既にプールにあるかどうかを調べることもできます。文字列がプールにある場合、IsInterned は true を返します。

GetHashCode

前回Equals をオーバーライドする場合、GetHashCode もオーバーライドする必要があるという考えを説明しました。String では、Equals はオーバーライドされ、GetHashCode もオーバーライドされるので、適切なハッシュ処理が行われ、パフォーマンスが向上します。

StringBuilder

今まで説明したように、新しい文字列を作成して返す String のメソッドが数多く用意されています。想像がつくとおり、多数の文字列の割り当てと破棄を行うと、効率が悪くなることがあります。

原則として、特定の文字列に対して、新しい文字列の作成を伴う文字列操作を 1 回のみ実行するのであれば、String の適切なメソッド (または適切な Visual Basic .NET 関数) を問題なく使用できます。しかし、文字列操作を複数回実行する場合、その操作を System.Text.Stringbuilder で実行できるのであれば、まず自分の文字列から StringBuilder を作成します。その StringBuilder に対して目的の操作を複数回実行した後、StringBuilder に対して ToString を呼び出し、結果の文字列を取得します。前の例では、この操作をすべて 1 行で行っています。

StringBuilder 内で文字列を作成し、それを操作してから文字列に変換するという操作は頻繁に行われる作業です。後でその StringBuilder オブジェクトを変更しない限り、ToString の呼び出しで文字列が実際にコピーされることはありません。StringBuilder を使用するためのオーバーヘッドも通常は 2 回ではなく 1 回のコピー操作だけであり、大変効率的な処理です。操作を 1 回のみとすることを規則としたのは、このような理由によるものです。2 回以上の操作を行う場合も StringBuilder を使用すれば、少なくとも適切な処理は実現します。

性能上の制限がある String のオーバーロードよりも、StringBuilder で利用可能なオーバーロードの方がはるかに使いやすい場合も、StringBuilder を使用できます。

StringBuilder を使用するには、プログラム ファイルの先頭に using System.Text; (Visual Basic の場合は Imports System.Text) を記述します。

StringBuilder には、文字列でオブジェクトを初期化し、その容量と最大容量を設定するコンストラクタがあります (既定の最大容量は約 20 憶文字です。これより小さい値を設定することは無意味ではありませんが、そのように最大容量を設定しなくても問題はありません)。オブジェクトの容量は、最大容量を越えない限り、必要に応じて上方修正されます。StringBuilder を構築した後では、最大容量を修正できません。

StringBuilder には次の 4 つのプロパティがあります。Capacity は、読み取りと書き込みが可能です。MaxCapacity は読み取り専用です。Length は文字列の現在の長さを示し、このプロパティで変更できます。Chars はインデクサで、これを使用して個々の文字の読み取りと書き込みができます。EnsureCapacity メソッドを使用すると、容量が不足している場合に容量を増やすことができます。

Append メソッドを使用すると、文字列や任意の型 (多数のオーバーロードがあります) を StringBuilder の末尾に付加できます。別の型を渡した場合、その ToString メソッドが呼び出され、その結果が StringBuilder に追加されます。

AppendFormat メソッドを使用すると、StringBuilder に追加する文字列の書式を設定できます。書式設定は、String.Format および Console.WriteLine と同じです。

Insert の豊富なオーバーロードを使用すると、StringBuilder の任意の位置に文字列 (パラメータの ToString メソッドの呼び出しから計算されたものなど) を挿入できます。

Remove を使用すると、任意の位置から任意の数の文字を削除できます。

Replace を使用すると、個々の文字の置換や、既に説明したように部分文字列の置換が可能です。

最後に、ToString のオーバーロードは、StringBuilder (またはその指定された部分文字列) から新しい String オブジェクトを作成して返します。

System.Text のエンコーダとデコーダ

.NET Framework プログラムの文字列はすべて 16 ビットの Unicode として格納されます。また、Unicode がまだ普及していないため、他の文字エンコードから Unicode に、または Unicode から他の文字エンコードに変換しなければならないこともあります。

.NET Framework には、"エンコード" (Unicode 文字から他のエンコードによるバイト ブロックへの変換) および "デコード" (他のエンコードによるバイト ブロックから Unicode 文字への変換) を実行するクラスがあります。

サポートされている各エンコードには、ASCIIEncodingCodePageEncoding、UnicodeEncoding (ビッグエンディアンからリトルエンディアンへの変換に便利です)、UTF7Encoding、および UTF8Encoding というクラスがあります。

これらの各クラスには、1 つの配列のエンコードとデコードを一度に実行するためのエンコード用のメソッド (GetBytes など) とデコード用のメソッド (GetChars など) の両方があります。また、各クラスで GetEncoderGetDecoder がサポートされています。これらから返されるエンコーダとデコーダはシフト ステートを管理できるため、ストリームやブロックで使用できます。

正規表現

Visual Basic .NET では、Like ステートメントによる限定的な正規表現マッチングがサポートされています。また、.NET Framework では、System.Text.RegularExpressions 名前空間で、非常に洗練された正規表現処理がサポートされています。ここで重要なクラスは System.Text.RegularExpressions.RegEx です。このクラスは、コンパイルされた正規表現を表し、それを使用するためのメソッドを提供します。これらのメソッドには、文字列の中で正規表現の検索と置換を行うものや、文字列を文字列の配列に分割するものなどがあります。正規表現に関するドキュメントはまだ少ないので、ここでは詳しくは触れません。今後のコラムで紹介することになると思われます。

.NET Framework を試す

皆さんの .NET Framework 学習チームにはどのような方がいますか?

優れた指導者は、皆さんが 1 人で .NET を楽しむだけでなく、他の仲間とも共に作業することを望んでいます。その方が楽しいし、より多くのことを学ぶことができます。

ここで .NET Framework を試してみます。

ソース ファイル (http://msdn.microsoft.com/library/en-us/dnguinet/html/drguinet5testcode.asp?frame=true) を開きます。それを Visual Studio にコピーし、自由に変更します。

文字列、そして文字列の配列をいろいろ操作してみます。

ここでは StringBuilder を使用して文字列を操作します。そして、String の高度な関数を使用して、コマンド ラインなどの文字列を解析してください。

何か興味のあることを試してみたいと思ったら、さまざまな文字セットやコード ページのエンコードとデコードに挑戦してみてください。何か新しいことをやってみたければ、正規表現クラスを試してみてください。

今回のテーマと次回のテーマ

今回は文字列について説明しました。次回は、配列について説明します。



Page view tracker