C#の参照型変数の中には何が入っている?「参照」をイメージで理解しよう

広告

C#でクラスを使うと、次のようなコードを書きます。

Student s1 = new Student();

s1 の中には Student オブジェクトそのものが入っているの?

結論から言うと、入っているのはオブジェクトそのものではなく「参照」です。

この記事では、値型との違いを比べながら「参照とは何か」をイメージで理解していきます。

1. まずは値型から考えてみよう

次のコードを見てください。

int a = 10;
int b = a;

この場合、b には a の中に入っている 10 という値がコピーされます。

値型の変数aからbへ値10がコピーされ、それぞれが独立した値を持つ図

ab は、それぞれ別々に 10 という値を持っています。

ここがポイント

値型の変数には、値そのものが入っています。

2. 参照型では何が入っている?

今度はクラスを使ってみます。

class Student
{
    public string Name { get; set; }
    public int Score { get; set; }
}

そして、次のようにオブジェクトを作成して値を設定します。

Student s1 = new Student();

s1.Name = "田中";
s1.Score = 80;

このとき、s1 の中に Student オブジェクト全部が入っているわけではありません。

参照型変数s1がNameは田中、Scoreは80のStudentオブジェクトを参照する図

s1 には、「このStudentオブジェクトを見に行ってください」という情報が入っています。これが参照です。

3. s1をs2に代入するとどうなる?

次のコードを追加します。

Student s2 = s1;

ここで大切なのは、Student オブジェクトそのものがコピーされるわけではないことです。コピーされるのは参照です。

参照型変数s1とs2が同じStudentオブジェクトを参照する図

s1s2 は、同じ Student オブジェクトを見ています。

4. 実際に変更して確認してみよう

s2 を使って名前を変更してみます。

s2.Name = "山田";

Console.WriteLine(s1.Name);

実行結果は次のようになります。

山田

s2 しか変更していないのに、s1.Name も「山田」になりました。これは、s1s2同じStudentを参照しているからです。

5. 同じオブジェクトなのか調べてみよう

通常のクラスで == 演算子を特別に定義していなければ、次の比較は True になります。

Console.WriteLine(s1 == s2);  // True

より明確に「同じオブジェクトを参照しているか」を確認するなら、object.ReferenceEquals を使います。

Console.WriteLine(object.ReferenceEquals(s1, s2));  // True

ReferenceEquals は、「この2つは本当に同じオブジェクトを見ていますか?」を確認するためのメソッドです。

なお、== は型によって比較の意味を変更できます。そのため、参照先そのものが同一かを確実に調べたい場合は ReferenceEquals が適しています。

6. 参照はアドレスなの?

参照って、メモリアドレスのこと?

イメージとしてはかなり近いです。初学者のうちは、参照 = オブジェクトの場所を示す住所のようなものと考えて問題ありません。

ただし厳密には、参照は固定されたメモリアドレスそのものではありません。.NETでは、GC(ガベージコレクション)がメモリを整理するときに、オブジェクトを別の場所へ移動させることがあります。そのため、C#では通常「アドレス」ではなく参照と呼びます。

7. メモリアドレスを一度見てみよう

普段のC#では、参照型変数の生のアドレスを意識する必要はありません。ただし、学習として「メモリには実際に場所がある」と確認するのは面白い実験です。

たとえば配列なら、unsafefixed を使ってアドレスを見ることができます。

以下はC#9.0以上が必要です

unsafe
{
    int[] numbers = { 10, 20, 30 };

    fixed (int* p = numbers)
    {
        Console.WriteLine($"アドレス: {(nint)p:X}");
        Console.WriteLine($"最初の値: {*p}");
        Console.WriteLine($"次の値: {*(p + 1)}");
    }
}

実行結果は環境によって異なりますが、次のように表示されます。

アドレス: 1F8A34C1050
最初の値: 10
次の値: 20

アドレスは実行するたびに変わることがあります。GCによって移動する可能性があるため、fixed を使って一時的に動かないようにしています。

注意:このコードをコンパイルするには、プロジェクトでアンセーフコードを許可する必要があります。Visual Studioでは、プロジェクトのプロパティから「アンセーフ コードの許可」を有効にしてください。

8. 普段はアドレスを知らなくても大丈夫

C#では、s1.Name と書くだけで参照先の Student オブジェクトへアクセスできます。プログラマが「このStudentは0x12345678番地にある」と意識する必要はありません。

.NETがメモリ管理を担ってくれるため、私たちはオブジェクトの意味や処理に集中できます。これがC#で参照を使う大きなメリットの1つです。

まとめ

  • 値型:変数の中に「値そのもの」が入る
  • 参照型:変数の中に「オブジェクトへの参照」が入る
  • Student s2 = s1; では、オブジェクト本体ではなく参照がコピーされる
  • 同じ参照先かを確実に確認するには object.ReferenceEquals を使う

まずは、「参照は、オブジェクトの場所を示す住所のようなもの」と覚えておけば十分です。その後、GCやメモリ管理を学んだときに、「固定された住所そのものではなく、.NETが管理している参照なんだ」と理解を深めていきましょう。

訪問数 9 回, 今日の訪問数 9回

広告

C#,初学者

Posted by hidepon