C#
 Computer >> コンピューター >  >> プログラミング >> C#

C#の値型と参照型の違いを徹底解説!メモリの仕組みとサンプルコードで比較

C#におけるすべての型は、大きく分けて「値型」「参照型」という2つのカテゴリに分類されます。この2つの違いを正しく理解することは、バグの防止やパフォーマンスの最適化において非常に重要です。本記事では、それぞれの型の特徴、動作の仕組み、そして実際のコード例を使った挙動の違いについて詳しく解説します。

値型とは

値型の変数は、データそのものを直接保持します。各変数が独自のデータコピーを持つため、ある変数の値を変更しても、別の変数やオブジェクトには一切影響しません。

C#における値型には、以下のようなものがあります。

  • int、float、double などのすべての数値型
  • char 型および bool
  • struct(構造体)型
  • enum(列挙型)

値型は単純に「値そのもの」を格納します。たとえば整数型の場合、変数には数値そのものが入ります。参照型のように「数値へのポインタ(参照)」が格納されるわけではありません。

構造体によるカスタム値型の定義

独自の値型を作成したい場合は、struct を使用します。以下はその例です。

public struct Point
{
    public int X;
    public int Y;
}
var p1 = new Point();

このとき、Point のインスタンスはメモリ上では次のように表現されます。変数 p1 には X と Y の値がスタック領域に直接格納されているイメージです。

C#の値型と参照型の違いを徹底解説!メモリの仕組みとサンプルコードで比較

値型の代入動作:値がコピーされる

値型の変数を別の変数に代入すると、代入操作によって値そのものが複製(コピー)されます。

Point p2 = p1;

C#の値型と参照型の違いを徹底解説!メモリの仕組みとサンプルコードで比較

このように、p1 と p2 はまったく独立した2つのデータを持つことになります。そのため、片方の値を変更しても、もう片方には影響しません。

参照型とは

一方、参照型の変数は、オブジェクトそのものではなくオブジェクトへの参照(アドレス)を格納します。そのため、2つの異なる変数が同じオブジェクトを参照することが可能です。この場合、一方の変数を通じてオブジェクトに行われた変更は、もう一方の変数からも見えることになります。

C#における主な参照型は以下のとおりです。

  • String(文字列)
  • Class(クラス)
  • Array(配列)
  • Delegate(デリゲート)
  • Interface(インターフェース)

クラスによる参照型の定義

たとえば、参照型であるクラスを作成する場合は以下のように記述します。

public class Point
{
    public int X;
    public int Y;
}
var p1 = new Point();

C#の値型と参照型の違いを徹底解説!メモリの仕組みとサンプルコードで比較

参照型の場合、オブジェクトの実体はヒープ領域に配置され、変数にはそのオブジェクトへの参照のみが格納されます。

参照型の代入動作:参照がコピーされる

参照型の変数を別の変数に代入すると、コピーされるのはオブジェクトそのものではなく、参照(アドレス)だけです。

Point p2 = p1;

C#の値型と参照型の違いを徹底解説!メモリの仕組みとサンプルコードで比較

この結果、p1 と p2 は同じオブジェクトを指すことになります。どちらか一方を経由してプロパティを変更すると、もう一方からもその変更が確認できます。

値型と参照型の挙動を確認するサンプルコード

ここまでの内容を理解するために、値型(struct)と参照型(class)の挙動の違いを1つのプログラムで確認してみましょう。

using System;
class Program
{
    static void Main()
    {
        var p1 = new Point { X = 10 };
        Point p2 = p1;
        p2.X = 20;
        Console.WriteLine("Value Type");
        Console.WriteLine(p1.X);
        Console.WriteLine(p2.X);

        var u1 = new User { Age = 10 };
        User u2 = u1;
        u2.Age = 20;
        Console.WriteLine("Reference Type");
        Console.WriteLine(u1.Age);
        Console.WriteLine(u2.Age);
    }
}
public struct Point
{
    public int X;
    public int Y;
}
public class User
{
    public int Age { get; set; }
}

実行結果

Value Type
10
20
Reference Type
20
20

実行結果の解説

値型(Point 構造体)の場合:p1 を p2 に代入した時点で値がコピーされるため、p2.X を 20 に変更しても p1.X は元のまま 10 です。つまり、両者は完全に独立しています。

参照型(User クラス)の場合:u1 を u2 に代入すると参照が共有されるため、u2.Age を 20 に変更すると u1.Age も 20 になります。両者は同じオブジェクトを指しているからです。

まとめ:値型と参照型の違い一覧

項目値型参照型
変数が保持するものデータそのものオブジェクトへの参照
代入時の動作値がコピーされる参照がコピーされる
主な格納場所スタックヒープ(変数自体はスタック)
代表例int、bool、struct、enumclass、string、配列、delegate、interface
他の変数への影響なし(独立している)あり(同一オブジェクトを共有)

値型と参照型の違いを理解しておくと、「なぜ意図せずデータが書き換わってしまうのか」といったバグの原因特定や、不要なオブジェクト生成を抑えたパフォーマンスチューニングにも役立ちます。用途に応じて struct と class を適切に使い分けましょう。

  1. C言語の参照とポインタとは?違いをサンプルコード付きでやさしく解説

    はじめに Cプログラミングを学ぶうえで避けて通れないのが「参照」と「ポインタ」の概念です。本記事では、それぞれの特徴・構文・違いを、実際に動くサンプルコードとともにわかりやすく解説します。 参照とは 参照とは、宣言済みの変数につけられた「別名(エイリアス)」のようなものです。主な特徴は次のとおりです。 宣言した変数の別名として振る舞う 値渡し(pass by value)のように扱える null値を持つことができない ※補足:厳密には「参照」はC++の機能であり、純粋なC言語には変数の別名を作る構文は存在しません。C言語で変数を間接的に操作するための仕組みが、後述する「ポインタ」です。

  2. 具体例でわかる!C言語のvolatile型修飾子とrestrict型修飾子の使い方

    型修飾子とは、C言語において既存のデータ型に特別な属性を付与するキーワードのことです。C言語には「const」「volatile」「restrict」の3種類の型修飾子が存在します。本記事では、このうちvolatileとrestrictについて、具体例を交えながらわかりやすく解説します。 volatile型修飾子とは volatile型修飾子は、「その変数が共有されている」ことをコンパイラに伝えるために使用します。つまり、変数をvolatile付きで宣言すると、その変数は他のプログラムやハードウェアなどの外部要因によって参照・変更される可能性があることを意味します。これにより、コンパイラはその