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は値型、stringclassから作ったオブジェクトは参照型です。

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つを解説します。

値型と参照型の違い
代入やメソッド引数で何がコピーされるのか
refoutinの基本的な違い

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の値である10bにコピーされます。
その後でb20に変更しても、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オブジェクトが丸ごとコピーされるわけではありません。コピーされるのは、オブジェクトの場所を示す参照です。

そのため、p1p2は同じオブジェクトを指します。

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

xyは別々の値を持っています。

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 ──┘

user1user2は同じオブジェクトを指しているため、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の中でx100にしても、呼び出し元のnumberは変わりません。

呼び出し元:
number ── 10

メソッド内:
x ── 10 → 100

xnumberのコピーなので、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, structclass, 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の違い

refoutは、どちらも引数を参照渡しするためのキーワードです。

ただし、使い方に違いがあります。

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. それぞれを使う場面

refoutinの使い分けは、次のように考えるとわかりやすいです。

キーワード主な目的使う場面
ref呼び出し元の変数を読み書きする値を更新したいとき
outメソッド内で値を設定して返す複数の結果を返したいとき
inコピーを避けつつ読み取り専用で渡す大きな値型を安全に渡したいとき

初心者がまず理解すべきなのはrefです。
outTryParseなどでよく出てきます。
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

これは、nameslistが同じ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());
}

このように書けば、refoutを使わなくても、複数の値をわかりやすく返せます。

まとめ

C# referenceという言葉には、主に3つの意味があります。

1つ目は、C# language referenceという公式ドキュメントとしてのreference。
2つ目は、参照型としてのreference。
3つ目は、refキーワードとしてのreferenceです。

C#の型は、大きく値型と参照型に分かれます。

値型は、変数が値そのものを持ちます。
参照型は、変数がオブジェクトへの参照を持ちます。

代入したとき、値型では値そのものがコピーされます。
参照型では、オブジェクト本体ではなく参照がコピーされます。

メソッドに引数を渡すとき、C#では基本的に値渡しです。
参照型を渡した場合でも、通常は参照のコピーが渡されます。

refを使うと、呼び出し元の変数そのものをメソッド内で変更できます。
ただし、refは便利な反面、使いすぎるとコードが読みにくくなるため、必要な場面に限定して使うことが大切です。

最後に、初心者向けに一言でまとめると、次のようになります。

値型は、値をコピーする。
参照型は、場所をコピーする。
refは、変数そのものを渡す。

この3つを区別できるようになると、C#の代入、メソッド引数、参照型の挙動がかなり理解しやすくなります。