VB.NETなどの元となった言語であるBASICには、プリミティブ型として文字列型が存在していました。 この文字列型は他の言語にはない使いやすさがあり、多少の仕様変更を伴いながらもBASICからQucik BASIC、Visual Basicと変わることなく受け継がれてきました。 Visual Basic .NETおよびC#でももちろん文字列型を扱うことができますが、VBの文字列型と比較すると格段に機能が向上しています。 .NET Frameworkでは文字列に関する処理をまとめ直し、文字列型はStringクラスとして存在するようになり、様々な機能が追加されています。

Stringクラスの個々の機能を説明する前に、.NET FrameworkにおけるStringクラスの位置づけと挙動、言語との関わりについてざっと見ておきます。

文字列型とStringクラス

.NET Frameworkにおける文字列型では、System.Stringクラスでその機能が提供されます。 例えば文字列の長さを調べたり、連結や分割などの加工を行ったりする場合にはStringクラスのプロパティやメソッドを利用することになります。 (Lenstrcatのような個別の関数は使いません)

文字列操作の例
using System;

class Sample {
  static void Main()
  {
    // 文字列変数sに文字列"foobarbaz"を代入する
    var s = "foobarbaz";

    // 文字列sの長さを表示する
    Console.WriteLine(s.Length);

    // 文字列sの6文字目から3文字分を切り出して表示する
    Console.WriteLine(s.Substring(6, 3));
  }
}
文字列操作の例
Imports System

Class Sample
  Shared Sub Main()
    ' 文字列変数sに文字列"foobarbaz"を代入する
    Dim s As String = "foobarbaz"

    ' 文字列sの長さを表示する
    Console.WriteLine(s.Length)

    ' 文字列sの6文字目から3文字分を切り出して表示する
    Console.WriteLine(s.Substring(6, 3))
  End Sub
End Class
実行結果
9
baz

次のコードが示すように、C#の組み込み型であるstring、VB.NETの組み込み型であるStringは、どちらも.NET FrameworkにおけるSystem.Stringクラスのエイリアス(別名)であるため、両者は全く同じ型となります。

組み込み文字列型とSystem.String
using System;

class Sample {
  static void Main()
  {
    // C#のstring型は.NET FrameworkのSystem.Stringクラスのエイリアス
    string s1 = "foo";
    // s1とs2はどちらもSystem.Stringとなる
    System.String s2 = "bar";
    // System.Stringをstringに代入することができる(その逆もまた可)
    string s3 = new System.String(new char[] {'b', 'a', 'z'});

    // GetType()が返す型はいずれもSystem.Stringとなる
    Console.WriteLine("{0} {1}", s1, s1.GetType());
    Console.WriteLine("{0} {1}", s2, s2.GetType());
    Console.WriteLine("{0} {1}", s3, s3.GetType());
  }
}
組み込み文字列型とSystem.String
Imports System

Class Sample
  Shared Sub Main()
    ' VB.NETのString型は.NET FrameworkのSystem.Stringクラスのエイリアス
    Dim s1 As String = "foo"
    ' s1とs2はどちらもSystem.Stringとなる
    Dim s2 As System.String = "bar"
    ' System.Stringをstringに代入することができる(その逆もまた可)
    Dim s3 As String = New System.String(New Char(){"b", "a", "z"})

    ' GetType()が返す型はいずれもSystem.Stringとなる
    Console.WriteLine("{0} {1}", s1, s1.GetType())
    Console.WriteLine("{0} {1}", s2, s2.GetType())
    Console.WriteLine("{0} {1}", s3, s3.GetType())
  End Sub
End Class
実行結果
foo System.String
bar System.String
baz System.String

したがって、string型/String型として宣言した変数と、System.Stringとして宣言した変数はどちらも同じ型となるため、相互に代入可能で、かつどちらも同じメソッド・プロパティを使用することができ、全く同じように扱うことができます。

各言語の組み込み型と.NET Frameworkの型との対応については型の種類・サイズ・精度・値域 §.基本的なデータ型と対応する各言語の型を参照してください。

静的メソッドを呼び出す場合も同様で、文字列型またはSystem.Stringクラスのメソッドとして呼び出しても全く同じ動作となります。

文字列型の静的メソッド呼び出し
using System;

class Sample {
  static void Main()
  {
    var s1 = "foo";
    var s2 = "bar";

    // Equalsメソッドを使って2つの文字列が等しいか比べる
    var b1 = string.Equals(s1, s2);
    var b2 = System.String.Equals(s1, s2);

    Console.WriteLine(b1);
    Console.WriteLine(b2);
  }
}
文字列型の静的メソッド呼び出し
Imports System

Class Sample
  Shared Sub Main()
    Dim s1 As String = "foo"
    Dim s2 As String = "bar"

    ' Equalsメソッドを使って2つの文字列が等しいか比べる
    Dim b1 As Boolean = String.Equals(s1, s2)
    Dim b2 As Boolean = System.String.Equals(s1, s2)

    Console.WriteLine(b1)
    Console.WriteLine(b2)
  End Sub
End Class
実行結果
False
False

stringとString

VB.NETではSystem名前空間をインポートすればどちらもStringという表記になり組み込み型のStringStringクラスとの区別が曖昧になります。

一方C#では、同じ文字列型でも組み込み型を表すstringとSystem.Stringを表すStringの両方の表記を混在させることができます。

既に説明したようにstringStringのどちらを使っても同じですが、C#では一般に、変数や引数、メンバなどの宣言ではstringを使い、静的メソッドを呼び出す場合などにはStringを使う、という使い分けをすることが多いようです。

比較的よく見られるstringとStringの使い分け
using System;

class Sample {
  static void Main()
  {
    // 変数の宣言ではstringを使う
    string s1 = "foo";
    string s2 = "bar";

    // 静的メソッドの呼び出しではStringを使う
    bool b1 = String.Equals(s1, s2);

    Console.WriteLine(b1);
  }
}

逆に、次のような使い分けをすることもできます。 表記が異なるだけで上記のコードと以下のコードは全く等価です。

あまり見られないstringとStringの使い分け
using System;

class Sample {
  static void Main()
  {
    // 変数の宣言ではStringを使う
    String s1 = "foo";
    String s2 = "bar";

    // 静的メソッドの呼び出しではstringを使う
    bool b1 = string.Equals(s1, s2);

    Console.WriteLine(b1);
  }
}

当然、stringStringのどちらかだけに統一することもできますが、ガイドラインやコーディング規約などで定められている場合は、それに合わせて統一することが推奨されます。

インデックス

System.Stringクラスでは、インデックスは0から始まります(0-based)。 例えば"ABC"という文字列の場合、Aのインデックスは0になります(1ではなく)。 つまり配列におけるインデックスと同様になります。 Stringクラスの文字列操作メソッドでは、配列同様にインデックスを0から始まるものとして扱います。

文字列操作とインデックス
using System;

class Sample {
  static void Main()
  {
    var s = "The quick brown fox jumps over the lazy dog";

    // インデックス0の文字(文字列sの1文字目)を表示する
    Console.WriteLine(s[0]);

    // インデックス4から5文字分を切り出して表示する
    Console.WriteLine(s.Substring(4, 5));

    // 部分文字列"quick"がある位置のインデックスで取得して表示する
    Console.WriteLine(s.IndexOf("quick"));
  }
}
文字列操作とインデックス
Imports System

Class Sample
  Shared Sub Main()
    Dim s As String = "The quick brown fox jumps over the lazy dog"

    ' インデックス0の文字(文字列sの1文字目)を表示する
    Console.WriteLine(s(0))

    ' インデックス4から5文字分を切り出して表示する
    Console.WriteLine(s.Substring(4, 5))

    ' 部分文字列"quick"がある位置のインデックスで取得して表示する
    Console.WriteLine(s.IndexOf("quick"))
  End Sub
End Class
実行結果
T
quick
4

インデックスの扱いは、C#やVBといった言語の違いに関わらず、.NET Frameworkですべて共通しています。 なお、Visual Basic 6までは文字列のインデックスは1から始まる(1-based)とされているため、LenInStrといったVBの文字列関数を使ったコードを移植する場合は特に注意が必要です。

n文字目という表記とインデックスの対応について注意を要する場合があります。 例えば、1文字目という表記が文字列のインデックス0を表す場合もあれば、文字の位置とインデックスを対応させるために0文字目と表記してインデックス0を指す場合もあります。 一般には前者であることが多いようですが、ドキュメントや文脈によっては後者が用いられる場合もあります。

VB.NETでも引き続きLen等のVB由来の文字列関数を使用し続けることはできます。 また、一般的には推奨されませんが、C#でもこれらの関数を使うことはできます。 詳しくはVBの文字列関数を参照してください。

参照型としての挙動

System.Stringはクラスであるため参照型になりますが、通常の参照型とは見かけ上の挙動が異なります。

参照型としての文字列型の挙動
using System;

class Sample {
  static void Main()
  {
    var s1 = "foo";
    var s2 = s1;

    Console.WriteLine("{0} {1}", s1, s2);

    s1 = "bar";

    Console.WriteLine("{0} {1}", s1, s2);
  }
}
参照型としての文字列型の挙動
Imports System

Class Sample
  Shared Sub Main()
    Dim s1 As String = "foo"
    Dim s2 As String = s1

    Console.WriteLine("{0} {1}", s1, s2)

    s1 = "bar"

    Console.WriteLine("{0} {1}", s1, s2)
  End Sub
End Class
実行結果
foo foo
bar foo

s2s1を代入した時点で両者は同じインスタンスを参照することになり、その後s1"bar"を代入すれば同じ参照となっているs2"bar"となる、という挙動を予想することもできますが、実際にはs2"foo"のままとなります。

このような結果となるのは、s1"bar"を代入している箇所で新しい文字列型のインスタンス"bar"が生成され、それがs1に代入されているためです。 上記のコードが次のようなコードで動作しているととらえるとその挙動が理解しやすいかもしれません(擬似コードのため実際には動作しません)。

参照型としての文字列型の挙動を理解するための疑似コード
using System;

class Sample {
  static void Main()
  {
    var s1 = new string("foo");
    var s2 = s1;

    Console.WriteLine("{0} {1}", s1, s2);

    s1 = new string("bar");

    Console.WriteLine("{0} {1}", s1, s2);
  }
}
参照型としての文字列型の挙動を理解するための疑似コード
Imports System

Class Sample
  Shared Sub Main()
    Dim s1 As String = New String("foo")
    Dim s2 As String = s1

    Console.WriteLine("{0} {1}", s1, s2)

    s1 = New String("bar")

    Console.WriteLine("{0} {1}", s1, s2)
  End Sub
End Class

参照と等価演算子

C#において、文字列型同士の等価演算子==、不等価演算子!=による比較では、参照の比較ではなく文字列の等価性の比較が行われます。 つまり==演算子では、両辺のインスタンスが異なっていても文字列として等しければ両辺は等しいものとして扱われます。

等価演算子による文字列同士の等価性の比較
using System;

class Sample {
  static void Main()
  {
    var s1 = "foo";
    var s2 = "bar";
    var s3 = "foo";

    Console.WriteLine(s1 == s1);
    Console.WriteLine(s1 == s2);
    Console.WriteLine(s1 == s3);
  }
}
実行結果
True
False
True

文字列の比較については、文字列の探索・比較 §.文字列の比較・等価性の検証で詳しく解説します。 参照の比較と値の比較の違いについては値型と参照型 §.同値性・同一性の比較を参照してください。


C#で文字列型同士の参照の比較(同一のインスタンスであるかどうかの比較)を行うには、いったんobjectにキャストして等価演算子==で比較するか、Object.ReferenceEqualsメソッドを使います。

等価演算子による文字列同士の参照の比較
using System;

class Sample {
  static void Main()
  {
    var s1 = "foo";
    var s2 = "bar";

    Console.WriteLine((object)s1 == (object)s2);
    Console.WriteLine(Object.ReferenceEquals(s1, s2));

    s1 = s2;

    Console.WriteLine((object)s1 == (object)s2);
    Console.WriteLine(Object.ReferenceEquals(s1, s2));
  }
}
実行結果
False
False
True
True

VB.NETで文字列型同士の参照の比較(同一のインスタンスであるかどうかの比較)を行うには、Is演算子を使います。 Is演算子では文字列の等価性の比較は行われません。

Is演算子による文字列同士の参照の比較
Imports System

Class Sample
  Shared Sub Main()
    Dim s1 As String = "foo"
    Dim s2 As String = "bar"

    Console.WriteLine(s1 Is s2)

    s1 = s2

    Console.WriteLine(s1 Is s2)
    Console.WriteLine(s1 Is "foo")
  End Sub
End Class
実行結果
False
True
False

関係演算子

C#では、関係演算子(<, >, <=, >=)による文字列の大小関係の比較はサポートされていません。 これらを使った文字列の比較はコンパイルエラーとなります。

C#では関係演算子を使った文字列の比較はできない
using System;

class Sample {
  static void Main()
  {
    var str1 = "abc";
    var str2 = "abb";

    if (str1 < str2) // error CS0019: 演算子 '<' を 'string' と 'string' 型のオペランドに適用することはできません。
      Console.WriteLine("str1 < str2");
    else if (str1 > str2) // error CS0019: 演算子 '>' を 'string' と 'string' 型のオペランドに適用することはできません。
      Console.WriteLine("str1 > str2");
    else
      Console.WriteLine("str1 == str2");
  }
}

このようにC#では関係演算子による文字列の比較はできませんが、代わりにString.Compareメソッドを使うことによって文字列の大小関係を比較することが出来ます。 このことについては文字列の探索・比較 §.比較 (CompareTo, Equals, Compare)で詳しく解説します。


VBでは、関係演算子を用いて文字列同士の大小関係を比較することが可能です。

関係演算子を使った文字列の比較
Imports System

Class Sample
  Shared Sub Main()
    Dim str1 As String = "abc"
    Dim str2 As String = "abb"

    If str1 < str2 Then
      Console.WriteLine("str1 < str2")
    Else If str1 > str2 Then
      Console.WriteLine("str1 > str2")
    Else
      Console.WriteLine("str1 = str2")
    End If
  End Sub
End Class
実行結果
str1 > str2