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

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

§1 文字列型とStringクラス

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

文字列操作の例
using System;

class Sample {
  static void Main()
  {
    string s = "foobarbaz";

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

    // 文字列sの6文字目から3文字分を切り出して表示する
    Console.WriteLine(s.Substring(6, 3));
  }
}
実行結果
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";
    System.String s2 = "bar";
    string s3 = new System.String(new char[] {'b', 'a', 'z'});

    Console.WriteLine("{0} {1}", s1, s1.GetType());
    Console.WriteLine("{0} {1}", s2, s2.GetType());
    Console.WriteLine("{0} {1}", s3, s3.GetType());
  }
}
実行結果
foo System.String
bar System.String
baz System.String

したがって、string型/String型として宣言した変数と、System.Stringとして宣言した変数はどちらも同じ型となり、相互に代入可能で、いずれも同じように扱うことができます。

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

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

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

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

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

    Console.WriteLine(b1);
    Console.WriteLine(b2);
  }
}
実行結果
False
False

§1.1 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のどちらかだけに統一することもできますが、ガイドラインやコーディング規約などで定められている場合は、それに合わせて統一することが推奨されます。



§2 参照型としての挙動

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

using System;

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

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

    s1 = "bar";

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

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

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

using System;

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

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

    s1 = new string("bar");

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

§3 参照と等価演算子

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

using System;

class Sample {
  static void Main()
  {
    string s1 = "foo";
    string s2 = "bar";
    string 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()
  {
    string s1 = "foo";
    string 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演算子による文字列同士の比較では参照の比較が行われます。 文字列の等価性の比較は行われません。

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

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

§4 関係演算子

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

using System;

class Sample {
  static void Main()
  {
    string str1 = "abc";
    string 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