4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C#の文字列比較(==演算子、参照比較、文字列プール)

Posted at

はじめに

C#の文字列において、主に比較操作について調べたことをまとめます。

==演算子のオーバーロード

C#では、文字列比較において==演算子を使うと、参照比較ではなく値比較をします。

Program.cs
// C#の文字列比較
string str1 = new string("hoge");
string str2 = new string("hoge");
Debug.WriteLine(str1.Equals(str2)); // true
Debug.WriteLine(str1 == str2);      // true ←値を比較するのでtrue

これは、Javaと比較した場合、特徴的な違いの一つです。

Sample.java
// Javaの文字列比較
class Sample {
    public static void main(String[] args) {
        String str1 = new String("hoge");
        String str2 = new String("hoge");
        System.out.println(str1.equals(str2)); // true
        System.out.println(str1 == str2);      // false ←参照を比較するのでfalse
    }
}

メモリ上の動きは、どちらのプログラムも同様ですが、先述の通り比較対象が異なります。

Equals() / equals() ==
C# 値比較 値比較
Java 値比較 参照比較

仕組み

❓なぜこのような動きをするのか
⇒C#のStringクラスでは、==演算子で値比較を行うよう、処理をオーバーロードしているためです。

前提として、他の参照型のオブジェクト(配列やobject型など)に対しては、==演算子を使用すると参照比較を行います。
これは、==演算子がオーバーロードされていない場合のデフォルトの動作です。

一方でStringクラスは、operator ==をオーバーロードすることで明示的に振る舞いを変えています。

image.png
↑実際に、Visual Studioから==演算子の定義を見てみると、オーバーロードされた処理が確認できます。
(他の参照型オブジェクトにおける==演算子では、このような実装はされていない)

image.png
↑さらに潜っていくと、Stringクラスの内部的な値比較ロジック(EqualsHelperメソッド)を呼び出していることが分かります。

実はこのロジックは、str1.Equals(str2)で使用しているEqualsメソッドからも呼び出されています。

このことから、Stringクラスでは、Equalsメソッドと==演算子、どちらも同じように値比較をしていることが分かります。

Equals() vs ==演算子

では何が違うかというと、==演算子の場合、nullを比較できるという特徴があります。

Program.cs
string str1 = null;
string str2 = new string("hoge");
Debug.WriteLine(str1.Equals(str2)); // NullReferenceException
Debug.WriteLine(str1 == str2);      // false

↑==演算子は静的メソッドとして実装されているため、左辺がnullでもNullReferenceExceptionが発生しません。

Program.cs
string str1 = null;
string str2 = null;
Debug.WriteLine(str1 == str2); // true

↑また、null同士の比較でtrueを得ることができます。

Equalsメソッドでも、以下のように使用すればnullセーフな比較が可能です。

Program.cs
str1?.Equals(str2) // null条件演算子を使用
string.Equals(str1, str2) // 静的メソッド版を使用

参照比較(object.ReferenceEquals())

では、C#で参照比較を行いたい時はどうするかというと、Objectクラスに実装されているReferenceEqualsメソッドを使用します。

Program.cs
string str1 = new string("hoge");
string str2 = new string("hoge");
Debug.WriteLine(object.ReferenceEquals(str1, str2)); // false

ちなみに、このReferenceEqualsメソッドは、先ほど見たEqualsメソッドや==演算子から、値比較ロジックよりに呼び出されていることが確認できます。

これは、「参照が等しければ、値が必ず等しくなる」が成り立つからだと考えられます。

文字列プール(インターンプール)

最後に、C#は文字列プールの機能を実装しています。

これは、プログラム内で同じ値の文字列リテラルが複数回使用された時に、同一オブジェクトを参照する仕組みです。(メモリ使用量の節約)

Program.cs
string str1 = "hoge";
string str2 = "hoge";
Debug.WriteLine(object.ReferenceEquals(str1, str2)); // true

この機能はJavaにも実装されています。

Sample.java
class Sample {
    public static void main(String[] args) {
        String str1 = "hoge";
        String str2 = "hoge";
        System.out.println(str1 == str2); // true
    }
}

メモリ上の動きとしては、ヒープエリアに内包されたインターンプールで文字列のオブジェクトを管理し、文字列リテラルが現れるとまずはインターンプールをチェックする、といった動きをするようです。

動作環境

  • Windows 11
  • C# 12.0
  • .NET 8.0
  • Visual Studio 2022
  • Java 21
4
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?