C# referenceとは?値型・参照型・refの違いを初心者向けに図解でわかりやすく解説
はじめに
「C# reference」と検索すると、いくつかの意味が混ざって出てきます。
C#におけるreferenceは、主に次の3つの文脈で使われます。
1つ目は、C#の公式ドキュメントである「C# language reference」のreference。
2つ目は、クラスや配列などの「参照型」としてのreference。
3つ目は、引数を参照渡しするためのrefキーワードとしてのreferenceです。
初心者が混乱しやすいのは、この3つがすべて「reference」という言葉で説明されることです。
この記事では、C# referenceという言葉を整理しながら、値型・参照型・refの違いを初心者向けにわかりやすく解説します。
1. C# referenceとは?まず3つの意味を整理しよう
1-1. 「C# reference」と検索する人が混同しやすいポイント
「C# reference」と調べる人が知りたい内容は、人によって異なります。
たとえば、次のような疑問が考えられます。
「C#の公式リファレンスを見たい」
「参照型と値型の違いを知りたい」
「refキーワードの意味を知りたい」
「参照渡しと値渡しの違いを知りたい」
これらは関連していますが、同じ意味ではありません。
特に初心者が混乱しやすいのは、「参照型」と「参照渡し」を同じものだと思ってしまうことです。C#では、参照型の変数も通常は値渡しでメソッドに渡されます。ここを理解すると、C#の代入や引数の動きが一気にわかりやすくなります。
1-2. C#言語リファレンスとしてのreference
1つ目の意味は、公式ドキュメントとしてのreferenceです。
Microsoft Learnには、C#の構文、キーワード、型、演算子などを説明する「C# language reference」があります。これは、C#の書き方を正確に確認したいときに使う辞書のようなものです。MicrosoftのC#言語リファレンスは、現在のC#言語仕様や新しい言語機能の情報を扱う公式資料です。
たとえば、次のような内容を調べるときに使います。
C#ref
out
in
class
struct
string
int
プログラミング中に「このキーワードの正確な意味は何だろう?」と思ったときは、C# language referenceを見ると正確な情報を確認できます。
1-3. 参照型としてのreference
2つ目の意味は、参照型としてのreferenceです。
C#には、大きく分けて「値型」と「参照型」があります。Microsoftの公式ドキュメントでも、値型の変数は値そのものを直接持ち、参照型の変数はデータ、つまりオブジェクトへの参照を持つと説明されています。
簡単にいうと、値型は「データそのものを入れる箱」、参照型は「データがある場所を示すメモ」のようなものです。
C#int number = 10; // 値型
string name = "Taro"; // 参照型
Person person = new Person(); // 参照型
intは値型、stringやclassから作ったオブジェクトは参照型です。
1-4. refキーワードとしてのreference
3つ目の意味は、refキーワードです。
refは、メソッドに引数を渡すときに「呼び出し元の変数そのものを操作できるようにする」ためのキーワードです。Microsoftのドキュメントでも、refはメソッドの引数を参照によって渡すときに使うキーワードとして説明されています。
たとえば、次のコードではnumberの値がメソッドの中で変更され、呼び出し元にも反映されます。
C#void Add(ref int x)
{
x += 10;
}
int number = 5;
Add(ref number);
Console.WriteLine(number); // 15
refを使うと、通常の値渡しとは違い、メソッド側から呼び出し元の変数を書き換えることができます。
1-5. この記事で解説する範囲
この記事では、主に次の3つを解説します。
値型と参照型の違い
代入やメソッド引数で何がコピーされるのかref、out、inの基本的な違い
C# referenceという言葉を理解するには、まず「値型」「参照型」「refキーワード」を分けて考えることが大切です。
2. C#の値型と参照型の違い
2-1. 値型とは
値型とは、変数の中に値そのものが入る型です。
代表的な値型には、次のようなものがあります。
C#int
double
bool
char
decimal
struct
enum
たとえば、次のコードを見てみましょう。
C#int a = 10;
int b = a;
b = 20;
Console.WriteLine(a); // 10
Console.WriteLine(b); // 20
b = a;と書くと、aの値である10がbにコピーされます。
その後でbを20に変更しても、aには影響しません。
値型では、それぞれの変数が独立した値を持っていると考えるとわかりやすいです。
2-2. 参照型とは
参照型とは、変数の中にデータそのものではなく、データがある場所への参照が入る型です。
代表的な参照型には、次のようなものがあります。
C#class
string
object
array
List<T>
delegate
interface
たとえば、次のようなクラスがあるとします。
C#class Person
{
public string Name { get; set; }
}
このクラスを使ってオブジェクトを作ると、変数にはオブジェクトそのものではなく、オブジェクトへの参照が入ります。
C#Person p1 = new Person();
p1.Name = "Taro";
イメージとしては、p1という変数が「Personオブジェクトの場所」を指している状態です。
2-3. 値型と参照型の代表例
値型と参照型は、次のように整理できます。
| 種類 | 代表例 | 特徴 |
|---|---|---|
| 値型 | int, double, bool, char, struct, enum | 値そのものを持つ |
| 参照型 | class, string, object, 配列, List<T> | オブジェクトへの参照を持つ |
ただし、stringは参照型ですが、後で説明するように不変、つまり一度作られた文字列の中身が変わらない性質を持つため、値型のように見えることがあります。
2-4. 図解:値型は値そのものを持つ
値型の変数は、値そのものを持っています。
C#int a = 10;
int b = a;
図で表すと、次のようなイメージです。
a ── 10
b ── 10
このあとbを変更しても、aとは別の箱なので影響しません。
C#b = 20;
a ── 10
b ── 20
値型では、代入すると値そのものがコピーされます。
2-5. 図解:参照型はデータの場所を持つ
参照型の変数は、オブジェクトの場所を持っています。
C#Person p1 = new Person();
p1.Name = "Taro";
Person p2 = p1;
図で表すと、次のようなイメージです。
p1 ──┐
├── Personオブジェクト { Name = "Taro" }
p2 ──┘
p2 = p1;と書いたとき、Personオブジェクトが丸ごとコピーされるわけではありません。コピーされるのは、オブジェクトの場所を示す参照です。
そのため、p1とp2は同じオブジェクトを指します。
C#p2.Name = "Jiro";
Console.WriteLine(p1.Name); // Jiro
Console.WriteLine(p2.Name); // Jiro
片方から中身を変更すると、もう片方から見ても変更されているように見えます。実際には、同じオブジェクトを見ているだけです。
3. 代入したときの違いをコードで理解する
3-1. 値型を代入した場合
値型を代入すると、値そのものがコピーされます。
C#int x = 100;
int y = x;
y = 200;
Console.WriteLine(x); // 100
Console.WriteLine(y); // 200
xとyは別々の値を持っています。
x ── 100
y ── 200
yを変更しても、xは変わりません。
3-2. 参照型を代入した場合
参照型を代入すると、参照がコピーされます。
C#class User
{
public string Name { get; set; }
}
User user1 = new User();
user1.Name = "Alice";
User user2 = user1;
user2.Name = "Bob";
Console.WriteLine(user1.Name); // Bob
Console.WriteLine(user2.Name); // Bob
user2 = user1;によって、user1が指しているオブジェクトへの参照がuser2にコピーされます。
user1 ──┐
├── Userオブジェクト { Name = "Bob" }
user2 ──┘
user1とuser2は同じオブジェクトを指しているため、user2.Nameを変更すると、user1.Nameから見ても変わったように見えます。
3-3. 片方を変更するともう片方に影響するケース
参照型で片方の変更がもう片方に影響するのは、「同じオブジェクトの中身を変更した場合」です。
C#User user1 = new User();
user1.Name = "Alice";
User user2 = user1;
user2.Name = "Bob";
Console.WriteLine(user1.Name); // Bob
一方で、変数そのものに別のオブジェクトを代入した場合は、話が変わります。
C#user2 = new User();
user2.Name = "Charlie";
Console.WriteLine(user1.Name); // Bob
Console.WriteLine(user2.Name); // Charlie
この場合、user2は新しいオブジェクトを指すようになります。
user1 ── Userオブジェクト { Name = "Bob" }
user2 ── Userオブジェクト { Name = "Charlie" }
参照型で重要なのは、「オブジェクトの中身を変更しているのか」「変数が指す先を変更しているのか」を区別することです。
3-4. 初心者がつまずきやすい「コピーされたもの」の違い
値型と参照型の違いは、「何がコピーされるか」で考えると理解しやすくなります。
値型では、値そのものがコピーされます。
int a = 10;
int b = a;
コピーされるもの:10という値
参照型では、参照がコピーされます。
User user2 = user1;
コピーされるもの:Userオブジェクトの場所を示す参照
つまり、参照型の代入ではオブジェクト本体が複製されるわけではありません。複製されるのは、オブジェクトを指すための情報です。
4. メソッドに引数を渡したときの違い
4-1. C#の引数は基本的に値渡し
C#では、メソッドの引数は基本的に値渡しです。Microsoftの値型に関する説明でも、代入、メソッドへの引数渡し、戻り値では、既定で変数の値がコピーされると説明されています。
ここでいう「値」とは、値型なら値そのもの、参照型なら参照です。
つまり、参照型を引数に渡した場合でも、通常は「参照のコピー」が渡されています。これが非常に重要です。
4-2. 値型を引数に渡した場合
値型をメソッドに渡すと、値がコピーされます。
C#void ChangeNumber(int x)
{
x = 100;
}
int number = 10;
ChangeNumber(number);
Console.WriteLine(number); // 10
ChangeNumberの中でxを100にしても、呼び出し元のnumberは変わりません。
呼び出し元:
number ── 10
メソッド内:
x ── 10 → 100
xはnumberのコピーなので、xを変更してもnumberには影響しません。
4-3. 参照型を引数に渡した場合
参照型をメソッドに渡すと、参照がコピーされます。
C#void ChangeName(User user)
{
user.Name = "Bob";
}
User user1 = new User();
user1.Name = "Alice";
ChangeName(user1);
Console.WriteLine(user1.Name); // Bob
この場合、userにはuser1が持っていた参照のコピーが入ります。
user1 ──┐
├── Userオブジェクト { Name = "Alice" }
user ──┘
メソッド内のuserと呼び出し元のuser1は、同じオブジェクトを指しています。
そのため、user.Nameを変更すると、呼び出し元から見ても変更が反映されます。
4-4. オブジェクトの中身を変更する場合
参照型の引数で、オブジェクトの中身を変更すると呼び出し元にも影響します。
C#void Rename(User user)
{
user.Name = "Ken";
}
User user1 = new User();
user1.Name = "Taro";
Rename(user1);
Console.WriteLine(user1.Name); // Ken
このコードでは、user1が指すオブジェクトのNameプロパティが変更されています。
つまり、変更しているのは変数そのものではなく、変数が指しているオブジェクトの中身です。
4-5. 参照先そのものを変更する場合
一方で、メソッド内で引数に新しいオブジェクトを代入しても、通常は呼び出し元には反映されません。
C#void ReplaceUser(User user)
{
user = new User();
user.Name = "New User";
}
User user1 = new User();
user1.Name = "Old User";
ReplaceUser(user1);
Console.WriteLine(user1.Name); // Old User
メソッド内でuser = new User();としても、変更されるのはメソッド内のローカル変数userだけです。
呼び出し前:
user1 ── Userオブジェクト { Name = "Old User" }
メソッド内で代入後:
user1 ── Userオブジェクト { Name = "Old User" }
user ── Userオブジェクト { Name = "New User" }
参照型でも、参照そのものは値渡しでコピーされているため、メソッド内で参照先を入れ替えても呼び出し元の変数は変わりません。
5. C#のrefキーワードとは
5-1. refの基本的な意味
refは、引数を参照渡しするためのキーワードです。
通常の引数渡しでは、値型なら値がコピーされ、参照型なら参照がコピーされます。
しかしrefを使うと、メソッド側から呼び出し元の変数そのものを扱えるようになります。
C#void Change(ref int x)
{
x = 100;
}
int number = 10;
Change(ref number);
Console.WriteLine(number); // 100
refを使う場合は、メソッド定義側と呼び出し側の両方にrefを書く必要があります。また、refに渡す変数は事前に初期化されている必要があります。Microsoftのドキュメントでも、refパラメーターを使うにはメソッド定義と呼び出しの両方でrefを明示し、渡す引数は事前に初期化する必要があると説明されています。
5-2. refを使うと何が変わるのか
refを使うと、メソッド内の引数が呼び出し元の変数と同じものを指すようになります。
通常の値渡しでは、次のようなイメージです。
number ── 10
x ── 10 ← コピー
refを使うと、次のようなイメージになります。
number / x ── 10
xを変更すると、実際にはnumberを変更しているのと同じになります。
5-3. 値型にrefを使う例
値型にrefを使うと、メソッド内での変更が呼び出し元に反映されます。
C#void Double(ref int value)
{
value *= 2;
}
int number = 5;
Double(ref number);
Console.WriteLine(number); // 10
refなしの場合は、呼び出し元のnumberは変わりません。
C#void Double(int value)
{
value *= 2;
}
int number = 5;
Double(number);
Console.WriteLine(number); // 5
値型で呼び出し元の値を直接変更したい場合に、refが使われます。
5-4. 参照型にrefを使う例
参照型にもrefを使うことができます。
特に、呼び出し元の変数が指すオブジェクト自体を入れ替えたい場合に使います。
C#void Replace(ref User user)
{
user = new User();
user.Name = "New User";
}
User user1 = new User();
user1.Name = "Old User";
Replace(ref user1);
Console.WriteLine(user1.Name); // New User
refを使っているため、メソッド内でuser = new User();とすると、呼び出し元のuser1が指す先も変わります。
refなしの場合と比べてみましょう。
C#void Replace(User user)
{
user = new User();
user.Name = "New User";
}
User user1 = new User();
user1.Name = "Old User";
Replace(user1);
Console.WriteLine(user1.Name); // Old User
refなしでは、呼び出し元のuser1は変わりません。
5-5. refを使うと呼び出し元に変更が反映される理由
refを使うと、メソッドに変数のコピーではなく、変数そのものへの参照が渡されます。
そのため、メソッド内で引数を変更すると、呼び出し元の変数にも変更が反映されます。
値型の場合は、値そのものを書き換えられます。
C#int number = 10;
Change(ref number); // number自体が変更される
参照型の場合は、参照先そのものを入れ替えられます。
C#User user = new User();
Replace(ref user); // userが指すオブジェクトを変更できる
ここで重要なのは、参照型だから自動的に参照渡しになるわけではないということです。
参照型の通常の引数渡しは、あくまで「参照の値渡し」です。refを使うと、「変数そのものを参照渡し」できます。
6. 値型・参照型・refの違いを比較表で整理
6-1. 値型と参照型の違い
| 項目 | 値型 | 参照型 |
|---|---|---|
| 変数が持つもの | 値そのもの | オブジェクトへの参照 |
| 代表例 | int, bool, double, struct | class, string, 配列, List<T> |
| 代入時 | 値がコピーされる | 参照がコピーされる |
| 片方の変更 | 基本的にもう片方に影響しない | 同じオブジェクトの中身を変更すると影響する |
| null | 通常は入らない | 入ることがある |
値型と参照型の違いは、「変数の中に何が入っているか」で考えると理解しやすいです。
6-2. 値渡しと参照渡しの違い
| 項目 | 値渡し | 参照渡し |
|---|---|---|
| C#の通常動作 | はい | いいえ |
| 使うキーワード | なし | ref, out, in |
| 渡されるもの | 値のコピー | 変数への参照 |
| 呼び出し元への影響 | 基本的に限定的 | 変更が反映される |
| 例 | Method(x) | Method(ref x) |
C#では、何も指定しなければ基本的に値渡しです。
参照渡しにしたい場合は、refなどのキーワードを明示します。
6-3. 参照型とrefの違い
参照型とrefは、名前が似ているため混同しやすいですが、意味は違います。
| 項目 | 参照型 | ref |
|---|---|---|
| 分類 | 型の種類 | 引数の渡し方を変えるキーワード |
| 例 | class, string, 配列 | ref int x |
| 意味 | 変数がオブジェクトへの参照を持つ | 変数そのものをメソッドに渡す |
| 使う場所 | 型の宣言や変数 | メソッド引数など |
| 初心者の誤解 | 参照型は常に参照渡し | refは参照型専用 |
参照型は「データの持ち方」の話です。refは「メソッドへの渡し方」の話です。
6-4. 「参照型だからrefが必要」は間違い
参照型をメソッドに渡すとき、必ずrefが必要なわけではありません。
たとえば、オブジェクトの中身を変更するだけなら、refは不要です。
C#void UpdateName(User user)
{
user.Name = "Updated";
}
User user1 = new User();
user1.Name = "Old";
UpdateName(user1);
Console.WriteLine(user1.Name); // Updated
このコードではrefを使っていませんが、Nameの変更は反映されます。
一方で、呼び出し元の変数が指すオブジェクトを別のものに入れ替えたい場合は、refが必要です。
C#void ReplaceUser(ref User user)
{
user = new User();
user.Name = "New";
}
つまり、「参照型だからrefが必要」ではなく、「呼び出し元の変数そのものを書き換えたいときにrefが必要」と覚えましょう。
6-5. 初心者向けの覚え方
初心者は、次のように覚えるとわかりやすいです。
値型は、値をコピーする。
参照型は、場所をコピーする。refは、変数そのものを渡す。
もう少し具体的にいうと、次のようになります。
値型:
箱の中身をコピーする
参照型:
地図のコピーを渡す
ref:
箱そのものを相手に触らせる
このイメージを持っておくと、代入やメソッド引数の挙動が理解しやすくなります。
7. ref・out・inの違い
7-1. refとoutの違い
refとoutは、どちらも引数を参照渡しするためのキーワードです。
ただし、使い方に違いがあります。
refは、呼び出し前に変数を初期化しておく必要があります。outは、呼び出し前に初期化していなくても使えますが、メソッド内で必ず値を代入する必要があります。
C#void SetValue(out int x)
{
x = 100;
}
int number;
SetValue(out number);
Console.WriteLine(number); // 100
outは、メソッドから複数の値を返したいときによく使われます。
代表例はint.TryParseです。
C#string text = "123";
bool success = int.TryParse(text, out int result);
Console.WriteLine(success); // True
Console.WriteLine(result); // 123
Microsoftのドキュメントでも、outはメソッドが引数に値を設定する場合に使うものとして説明されています。
7-2. refとinの違い
inは、引数を参照で渡しつつ、メソッド内で変更できないようにするためのキーワードです。
C#void Show(in int x)
{
Console.WriteLine(x);
// x = 100; // エラー
}
inを使うと、大きな値型をコピーせずに渡しながら、メソッド内で変更されないことを保証できます。
refは読み書きできます。inは基本的に読み取り専用です。
Microsoftのメソッドパラメーターの説明でも、refは読み書き可能、inは読み取り用として説明されています。
7-3. それぞれを使う場面
ref、out、inの使い分けは、次のように考えるとわかりやすいです。
| キーワード | 主な目的 | 使う場面 |
|---|---|---|
ref | 呼び出し元の変数を読み書きする | 値を更新したいとき |
out | メソッド内で値を設定して返す | 複数の結果を返したいとき |
in | コピーを避けつつ読み取り専用で渡す | 大きな値型を安全に渡したいとき |
初心者がまず理解すべきなのはrefです。outはTryParseなどでよく出てきます。inはパフォーマンスや設計を意識する場面で使われることが多いです。
7-4. 初心者はまずrefだけ理解すればよい理由
初心者のうちは、まずrefを理解すれば十分です。
なぜなら、refを理解すると、値渡しと参照渡しの違いが見えてくるからです。
outは「メソッド内で必ず値を入れるrefのようなもの」と考えられます。inは「読み取り専用のrefのようなもの」と考えられます。
もちろん厳密には違いがありますが、最初からすべてを完璧に覚える必要はありません。
まずは、次の3つを押さえましょう。
ref: 読み書きできる参照渡し
out: メソッド内で値を入れて返す
in : 読み取り専用で渡す
8. よくある間違いと注意点
8-1. 参照型は常に参照渡しという誤解
C#初心者が特によく間違えるのが、「参照型は常に参照渡し」と考えてしまうことです。
これは正確ではありません。
参照型の変数は、オブジェクトへの参照を持っています。
しかし、メソッドに渡すときは、通常その参照がコピーされます。
つまり、参照型の通常の引数渡しは「参照の値渡し」です。
C#void Replace(User user)
{
user = new User();
user.Name = "New";
}
User user1 = new User();
user1.Name = "Old";
Replace(user1);
Console.WriteLine(user1.Name); // Old
この例では、メソッド内でuserに新しいオブジェクトを代入しても、呼び出し元のuser1は変わりません。
8-2. refを使えば何でも安全に変更できるという誤解
refを使うと、呼び出し元の変数を変更できます。
しかし、それは便利である一方、コードの見通しを悪くすることもあります。
たとえば、次のようなコードは、メソッドを呼ぶだけで変数が変わります。
C#Update(ref count);
このような書き方が多くなると、どこで値が変更されたのか追いにくくなります。
refは強力ですが、使いすぎるとバグの原因になります。
戻り値で表現できる場合は、戻り値を使ったほうが読みやすいことが多いです。
C#int count = Update(count);
このほうが「更新された値を受け取っている」と読み取りやすくなります。
8-3. 配列やListで変更が反映される理由
配列やList<T>は参照型です。
そのため、メソッドに渡して中身を変更すると、呼び出し元にも反映されます。
C#void AddItem(List<string> names)
{
names.Add("Taro");
}
List<string> list = new List<string>();
AddItem(list);
Console.WriteLine(list.Count); // 1
これは、namesとlistが同じList<string>オブジェクトを指しているためです。
list ──┐
├── List<string> { "Taro" }
names ──┘
ただし、メソッド内で新しいリストを代入しても、refなしでは呼び出し元には反映されません。
C#void ReplaceList(List<string> names)
{
names = new List<string>();
names.Add("Jiro");
}
List<string> list = new List<string>();
ReplaceList(list);
Console.WriteLine(list.Count); // 0
8-4. stringが参照型なのに特殊に見える理由
stringは参照型です。
しかし、値型のように見えることがあります。
理由は、stringが不変、つまりイミュータブルな型だからです。
C#string name = "Taro";
string other = name;
other = "Jiro";
Console.WriteLine(name); // Taro
Console.WriteLine(other); // Jiro
このコードでは、other = "Jiro";によって文字列の中身が変更されたわけではありません。otherが新しい文字列を指すようになっただけです。
メソッドでも同じです。
C#void ChangeText(string text)
{
text = "Hello";
}
string message = "Hi";
ChangeText(message);
Console.WriteLine(message); // Hi
stringは参照型ですが、文字列自体を変更するのではなく、新しい文字列を作って参照を入れ替える動きになるため、値型のように見えるのです。
8-5. 不要なrefを使わないほうがよい理由
refは必要な場面では便利ですが、不要に使うべきではありません。
理由は、コードを読む人が「このメソッドは呼び出し元の変数を変更するかもしれない」と常に意識しなければならなくなるからです。
C#Calculate(ref price);
このようなコードは、priceがどのように変わるのか、メソッドの中を見ないとわかりにくい場合があります。
一方で、戻り値を使うと意図が明確になります。
C#price = Calculate(price);
この書き方なら、「計算結果をpriceに代入している」とすぐにわかります。
可読性を重視するなら、まずはrefなしで書けないかを考えるのがおすすめです。
9. 実践でどう使い分けるべきか
9-1. 通常はrefなしで書く
実践では、通常はrefなしで書くことが多いです。
値を変更したい場合でも、戻り値で返せるなら戻り値を使うほうがわかりやすくなります。
C#int AddTen(int value)
{
return value + 10;
}
int number = 5;
number = AddTen(number);
このコードは、何が起きているかが明確です。
一方、refを使うと次のようになります。
C#void AddTen(ref int value)
{
value += 10;
}
int number = 5;
AddTen(ref number);
この書き方も正しいですが、単純な処理なら戻り値のほうが自然です。
9-2. 値型と参照型は設計意図で選ぶ
値型と参照型は、単にパフォーマンスだけで選ぶものではありません。
設計意図で選ぶことが大切です。
値型は、小さくて単純な値を表すのに向いています。
C#int
bool
DateTime
decimal
struct Point
参照型は、状態や振る舞いを持つオブジェクトを表すのに向いています。
C#User
Order
Product
Service
Repository
たとえば、ユーザー情報や注文情報のように複数のプロパティを持ち、状態が変化するものはクラスとして設計することが多いです。
9-3. refを使うべきケース
refを使うべきケースは限られています。
たとえば、次のような場面です。
複数の値を効率よく更新したい場合
大きな構造体のコピーを避けたい場合
呼び出し元の変数そのものを入れ替えたい場合
低レベルな最適化が必要な場合
例として、2つの値を入れ替える処理があります。
C#void Swap(ref int a, ref int b)
{
int temp = a;
a = b;
b = temp;
}
int x = 1;
int y = 2;
Swap(ref x, ref y);
Console.WriteLine(x); // 2
Console.WriteLine(y); // 1
このように、呼び出し元の2つの変数を直接変更する必要がある場合は、refが適しています。
9-4. refを避けたほうがよいケース
次のような場合は、refを避けたほうがよいことが多いです。
戻り値で表現できる場合
メソッドの副作用を減らしたい場合
コードの読みやすさを優先したい場合
参照型オブジェクトの中身を変更するだけの場合
たとえば、次のような処理にrefは不要です。
C#void Rename(User user)
{
user.Name = "New Name";
}
Userは参照型なので、オブジェクトの中身を変更するだけならrefなしで十分です。
refが必要なのは、次のように変数が指す先そのものを変更したい場合です。
C#void Replace(ref User user)
{
user = new User();
}
9-5. 読みやすいC#コードにするための判断基準
読みやすいC#コードを書くためには、次の順番で考えるとよいです。
まず、通常の値渡しで書けないか考える。
次に、戻り値で表現できないか考える。
それでも必要な場合だけ、refを使う。
判断基準はシンプルです。
オブジェクトの中身を変えるだけ → refなし
呼び出し元の変数そのものを変える → refを検討
値を返すだけ → 戻り値を使う
複数の値を返す → outや戻り値の型を検討
最近のC#では、タプルや専用の戻り値クラスを使って複数の値を返すこともできます。
C#(int min, int max) GetMinMax(int[] numbers)
{
return (numbers.Min(), numbers.Max());
}
このように書けば、refやoutを使わなくても、複数の値をわかりやすく返せます。
まとめ
C# referenceという言葉には、主に3つの意味があります。
1つ目は、C# language referenceという公式ドキュメントとしてのreference。
2つ目は、参照型としてのreference。
3つ目は、refキーワードとしてのreferenceです。
C#の型は、大きく値型と参照型に分かれます。
値型は、変数が値そのものを持ちます。
参照型は、変数がオブジェクトへの参照を持ちます。
代入したとき、値型では値そのものがコピーされます。
参照型では、オブジェクト本体ではなく参照がコピーされます。
メソッドに引数を渡すとき、C#では基本的に値渡しです。
参照型を渡した場合でも、通常は参照のコピーが渡されます。
refを使うと、呼び出し元の変数そのものをメソッド内で変更できます。
ただし、refは便利な反面、使いすぎるとコードが読みにくくなるため、必要な場面に限定して使うことが大切です。
最後に、初心者向けに一言でまとめると、次のようになります。
値型は、値をコピーする。
参照型は、場所をコピーする。
refは、変数そのものを渡す。
この3つを区別できるようになると、C#の代入、メソッド引数、参照型の挙動がかなり理解しやすくなります。

