🎃

参照型についてわからないからAIに聞いてみたらいろいろ教えてくれた

2025/02/09に公開

参照型(reference type)

クラス(class)で宣言された型は参照型になります。string、List、配列などは参照型です。ヒープ領域に実体(インスタンス)があり、スタックにはそこへの参照(reference)のみが入っています。巨大なオブジェクトを値渡しすると発生する大量のコピーが防げるのがパフォーマンス上のメリットです。

c#の参照型についての用例を書いて

参照型(reference type)の特徴を理解するために、以下のC#の例を考えます。参照型は、実際のデータがヒープ領域に格納され、変数はそのデータへの参照(ポインタ)を保持します。これにより、巨大なオブジェクトを効率的に扱うことができます。

例: 参照型の使用

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        // 参照型の例: List<int>
        List<int> list1 = new List<int> { 1, 2, 3, 4, 5 };
        
        // list2はlist1と同じリストを参照する
        List<int> list2 = list1;

        // list2を変更すると、list1も変更される
        list2.Add(6);

        Console.WriteLine("list1の内容:");
        foreach (int item in list1)
        {
            Console.WriteLine(item);
        }

        Console.WriteLine("list2の内容:");
        foreach (int item in list2)
        {
            Console.WriteLine(item);
        }

        // 参照型の例: string
        string str1 = "Hello";
        string str2 = str1;

        // str2を変更しても、str1は変更されない (stringは不変(immutable)な型)
        str2 = "World";

        Console.WriteLine($"str1: {str1}");
        Console.WriteLine($"str2: {str2}");

        // 参照型の例: 配列
        int[] array1 = new int[] { 10, 20, 30 };
        int[] array2 = array1;

        // array2を変更すると、array1も変更される
        array2[0] = 100;

        Console.WriteLine("array1の内容:");
        foreach (int item in array1)
        {
            Console.WriteLine(item);
        }

        Console.WriteLine("array2の内容:");
        foreach (int item in array2)
        {
            Console.WriteLine(item);
        }
    }
}

出力結果


list1の内容:
1
2
3
4
5
6
list2の内容:
1
2
3
4
5
6
str1: Hello
str2: World
array1の内容:
100
20
30
array2の内容:
100
20
30

解説
List<int>の例:

list1とlist2は同じリストを参照しています。list2に要素を追加すると、list1も同じ変更を反映します。
stringの例:

stringは参照型ですが、不変(immutable)な型です。str2に新しい文字列を代入しても、str1は変更されません。これは、stringが不変であり、新しい文字列が作成されるためです。
配列の例:

array1とarray2は同じ配列を参照しています。array2の要素を変更すると、array1も同じ変更を反映します。
パフォーマンス上のメリット
巨大なオブジェクトのコピーを防ぐ: 値型の場合、オブジェクトを関数に渡す際にコピーが発生しますが、参照型では参照だけが渡されるため、パフォーマンスが向上します。
メモリ効率: 同じデータを複数の変数で共有できるため、メモリの使用量が節約されます。
このように、参照型はデータの効率的な管理とパフォーマンスの向上に役立ちます。

逆に参照型でないのは、単純なstring やintのようなものですか??

いいえ、少し誤解があります。実は、string は参照型です。一方、int や bool、double などの基本的な数値型や真偽値は 値型(value type) です。

以下に、参照型と値型の違いを整理します。

参照型(Reference Type)の特徴
データはヒープ領域に格納される。
変数はヒープ上のデータへの参照(ポインタ)を保持する。
複数の変数が同じデータを参照できる。
例: string, class, List<T>, 配列 (int[], string[] など), object など。
値型(Value Type)の特徴
データはスタック領域に直接格納される(ただし、クラスのメンバとして値型が使われる場合はヒープに格納される)。
変数はデータそのものを保持する。
変数をコピーすると、新しいデータが作成される。
例: int, bool, double, float, char, struct など。
具体例

値型の例: int

int a = 10;
int b = a; // bにaの値がコピーされる
b = 20;    // bを変更してもaには影響しない

Console.WriteLine(a); // 出力: 10
Console.WriteLine(b); // 出力: 20

参照型の例: string


string str1 = "Hello";
string str2 = str1; // str2はstr1と同じ文字列を参照する
str2 = "World";     // str2に新しい文字列を代入(str1は変更されない)

Console.WriteLine(str1); // 出力: Hello
Console.WriteLine(str2); // 出力: World

参照型の例: List<T>

List<int> list1 = new List<int> { 1, 2, 3 };
List<int> list2 = list1; // list2はlist1と同じリストを参照する
list2.Add(4);            // list2を変更するとlist1も変更される

Console.WriteLine(string.Join(", ", list1)); // 出力: 1, 2, 3, 4
Console.WriteLine(string.Join(", ", list2)); // 出力: 1, 2, 3, 4

string は参照型だが不変(immutable)である理由
string は参照型ですが、不変(immutable)な性質を持っています。つまり、一度作成された文字列は変更できません。文字列を変更する操作(例: str2 = "World")を行うと、新しい文字列がヒープ上に作成され、変数はその新しい文字列を参照します。このため、元の文字列は変更されません。

値型と参照型のまとめ

値型 (Value Type) 参照型 (Reference Type)
データの格納場所 スタック(通常) ヒープ
変数の内容 データそのもの データへの参照
コピーの動作 新しいデータが作成される 同じデータを参照する
変更の影響 コピー後に独立 同じデータを共有する

例 int, bool, struct string, class, 配列
補足: struct と class の違い
struct は値型です。データが直接スタックに格納され、コピー時に新しいデータが作成されます。
class は参照型です。データはヒープに格納され、変数はそのデータへの参照を保持します。
このように、string は参照型であり、int や bool などが値型です。

コピーしたいだけの場合はどうするの??

コピーしたい場合、参照型と値型で異なるアプローチが必要です。参照型の場合は、単純に代入すると参照がコピーされるだけで、データそのものは共有されます。データを完全にコピーするためには、明示的に新しいインスタンスを作成する必要があります。

以下に、コピーする方法を具体的に説明します。

1. 値型の場合

値型はデフォルトでコピーが作成されるため、単純に代入するだけで新しいデータが作成されます。

int a = 10;
int b = a; // bにaの値がコピーされる
b = 20;    // bを変更してもaには影響しない

Console.WriteLine(a); // 出力: 10
Console.WriteLine(b); // 出力: 20

2. 参照型の場合

参照型の場合、単純に代入すると参照がコピーされるだけで、データは共有されます。データを完全にコピーするためには、新しいインスタンスを作成する必要があります。以下に、いくつかの参照型のコピー方法を示します。

例1: List<T> のコピー


List<int> list1 = new List<int> { 1, 2, 3 };
List<int> list2 = new List<int>(list1); // list1の内容をコピーして新しいリストを作成

list2.Add(4); // list2を変更してもlist1には影響しない

Console.WriteLine(string.Join(", ", list1)); // 出力: 1, 2, 3
Console.WriteLine(string.Join(", ", list2)); // 出力: 1, 2, 3, 4

例2: 配列のコピー

int[] array1 = new int[] { 10, 20, 30 };
int[] array2 = (int[])array1.Clone(); // array1の内容をコピーして新しい配列を作成

array2[0] = 100; // array2を変更してもarray1には影響しない

Console.WriteLine(string.Join(", ", array1)); // 出力: 10, 20, 30
Console.WriteLine(string.Join(", ", array2)); // 出力: 100, 20, 30

例3: string のコピー

string は不変(immutable)な型なので、単純に代入しても新しい文字列が作成されます。明示的なコピーは不要です。

string str1 = "Hello";
string str2 = str1; // str2はstr1と同じ文字列を参照するが、不変なので安全

str2 = "World"; // str2に新しい文字列を代入(str1は変更されない)

Console.WriteLine(str1); // 出力: Hello
Console.WriteLine(str2); // 出力: World

例4: カスタムクラスのコピー

カスタムクラスの場合、ICloneable インターフェースを実装するか、手動で新しいインスタンスを作成します。

class Person : ICloneable
{
    public string Name { get; set; }
    public int Age { get; set; }

    public object Clone()
    {
        return new Person { Name = this.Name, Age = this.Age };
    }
}

Person person1 = new Person { Name = "Alice", Age = 25 };
Person person2 = (Person)person1.Clone(); // person1の内容をコピーして新しいインスタンスを作成

person2.Name = "Bob"; // person2を変更してもperson1には影響しない

Console.WriteLine($"person1: {person1.Name}, {person1.Age}"); // 出力: person1: Alice, 25
Console.WriteLine($"person2: {person2.Name}, {person2.Age}"); // 出力: person2: Bob, 25

3. ディープコピーとシャローコピー

参照型のコピーには、シャローコピー と ディープコピー の2種類があります。

シャローコピー: オブジェクトの参照のみをコピーします。内部的に参照型のフィールドがある場合、それらは共有されます。
ディープコピー: オブジェクトとその内部のすべての参照型フィールドを再帰的にコピーします。
上記の例では、List<T> や配列のコピーはシャローコピーです。ディープコピーが必要な場合は、すべての参照型フィールドを再帰的にコピーする必要があります。

まとめ

値型は単純に代入するだけでコピーが作成されます。
参照型は明示的に新しいインスタンスを作成する必要があります。
List<T> や配列は new List<T>(list) や Clone() メソッドでコピーできます。
カスタムクラスは ICloneable インターフェースを実装するか、手動でコピーします。
ディープコピーが必要な場合は、すべての参照型フィールドを再帰的にコピーします。

Discussion