C#のout引数とは?使い方・refとの違い・複数戻り値の実例を初心者向けに解説

はじめに

C#を学び始めると、メソッドの引数にoutが付いているコードを見かけることがあります。

C#
int number;
bool result = int.TryParse("123", out number);

このoutは「メソッドの中で値を代入し、その結果を呼び出し元に返す」ために使うキーワードです。

通常、C#のメソッドはreturnで1つの値を返します。しかし、処理の成功・失敗をboolで返しつつ、変換結果や取得結果を別の値として返したい場合があります。そのような場面でよく使われるのがout引数です。

この記事では、C#のout引数について、基本的な意味、使い方、refとの違い、複数戻り値の実例、よくあるエラーまで初心者向けに解説します。

1. C#のout引数とは?

1-1. out引数の基本的な意味

C#のout引数とは、メソッドの中で値を設定し、その値を呼び出し元へ返すための引数です。

通常の引数は、呼び出し元からメソッドへ値を渡すために使います。

C#
void ShowMessage(string message)
{
Console.WriteLine(message);
}

この例では、messageは呼び出し元からメソッドへ渡される値です。

一方、out引数は、メソッドの中で値を代入して、呼び出し元に結果を返します。

C#
void SetValue(out int value)
{
value = 100;
}

このメソッドを呼び出すと、value100が代入されます。

C#
int result;
SetValue(out result);

Console.WriteLine(result); // 100

つまり、out引数は「メソッドから外へ値を出すための引数」と考えると理解しやすいです。

1-2. out引数を使うと何ができるのか

out引数を使うと、主に次のようなことができます。

1つ目は、メソッドから複数の値を返せることです。

C#
void GetUser(out string name, out int age)
{
name = "Taro";
age = 25;
}

このように、nameageの2つの値を呼び出し元に返せます。

2つ目は、処理の成功・失敗と結果を分けて返せることです。

C#
bool TryGetNumber(out int number)
{
number = 10;
return true;
}

この例では、戻り値のboolで成功・失敗を表し、out引数で実際の結果を返しています。

C#では、int.TryParseDictionary.TryGetValueなど、標準ライブラリでもこのパターンがよく使われます。

1-3. 通常の戻り値との違い

通常の戻り値は、returnを使ってメソッドから値を返します。

C#
int Add(int a, int b)
{
return a + b;
}

呼び出し側は次のように結果を受け取ります。

C#
int result = Add(3, 5);
Console.WriteLine(result); // 8

一方、out引数では、戻り値ではなく引数を通して値を受け取ります。

C#
void Add(int a, int b, out int result)
{
result = a + b;
}

呼び出し側は次のようになります。

C#
int answer;
Add(3, 5, out answer);

Console.WriteLine(answer); // 8

通常の戻り値は「1つの結果を返す」ときに向いています。out引数は「戻り値とは別に追加の結果を返したい」ときに使います。

ただし、単純に1つの値を返すだけなら、基本的にはreturnを使うほうが自然です。

1-4. 初心者がoutでつまずきやすいポイント

初心者がoutでつまずきやすいポイントは、主に次の3つです。

まず、呼び出し側にもoutを書く必要があります。

C#
GetValue(out result);

メソッド定義側だけでなく、呼び出し側にもoutを書かないとエラーになります。

次に、メソッド内でout引数には必ず値を代入しなければなりません。

C#
void GetValue(out int value)
{
// valueに代入していないのでエラー
}

out引数は「値を返すための引数」なので、メソッドが終わるまでに値が確定している必要があります。

最後に、refとの違いが分かりにくい点です。outはメソッド内で値を設定して返すためのもの、refは呼び出し前の値をメソッド内で読み書きするためのものです。

この違いは後半で詳しく解説します。

2. C#のout引数の基本的な使い方

2-1. out引数の基本構文

out引数の基本構文は次のとおりです。

C#
戻り値の型 メソッド名(out  変数名)
{
変数名 = ;
}

例えば、整数値をoutで返すメソッドは次のように書きます。

C#
void GetNumber(out int number)
{
number = 100;
}

呼び出し側では、次のようにoutを付けて変数を渡します。

C#
int result;
GetNumber(out result);

Console.WriteLine(result); // 100

ポイントは、メソッド定義側と呼び出し側の両方にoutを書くことです。

2-2. メソッド定義側の書き方

メソッド定義側では、引数の型の前にoutを付けます。

C#
void CreateMessage(out string message)
{
message = "Hello, C#";
}

このmessageは、メソッドの中で必ず代入する必要があります。

次のように、条件分岐がある場合でも、すべてのルートで値を代入しなければなりません。

C#
void GetStatus(bool isSuccess, out string message)
{
if (isSuccess)
{
message = "成功しました";
}
else
{
message = "失敗しました";
}
}

このコードは、isSuccesstrueでもfalseでもmessageに値が入るため問題ありません。

一方、次のコードはエラーになります。

C#
void GetStatus(bool isSuccess, out string message)
{
if (isSuccess)
{
message = "成功しました";
}

// isSuccessがfalseの場合、messageに値が入らない
}

out引数はメソッド終了時に必ず値が設定されている必要があるためです。

2-3. メソッド呼び出し側の書き方

呼び出し側では、あらかじめ変数を用意して、その変数にoutを付けて渡します。

C#
void GetPrice(out int price)
{
price = 1200;
}

int itemPrice;
GetPrice(out itemPrice);

Console.WriteLine(itemPrice); // 1200

このとき、itemPriceはメソッド呼び出し前に初期化していなくても問題ありません。

C#
int itemPrice;
GetPrice(out itemPrice);

out引数は、メソッド側で値を代入することが前提だからです。

ただし、呼び出し後に使うには、メソッドが正常に実行されている必要があります。out引数に値が入るのは、メソッドの中で代入されたあとです。

2-4. out変数宣言を使った書き方

C#では、out引数を呼び出すときに、その場で変数を宣言できます。これをout変数宣言と呼びます。

C#
GetPrice(out int itemPrice);

Console.WriteLine(itemPrice);

従来の書き方では、先に変数を宣言してからメソッドを呼び出していました。

C#
int itemPrice;
GetPrice(out itemPrice);

out変数宣言を使うと、コードを短く書けます。

特にTryParseではよく使われます。

C#
if (int.TryParse("123", out int number))
{
Console.WriteLine(number);
}

さらに、型推論を使ってout varとも書けます。

C#
if (int.TryParse("123", out var number))
{
Console.WriteLine(number);
}

out varを使うと、変数の型をコンパイラが推論してくれます。int.TryParseの場合、numberint型になります。

2-5. out引数に値を代入しないとエラーになる理由

out引数に値を代入しないとエラーになる理由は、呼び出し元が「メソッドの実行後には値が入っている」と期待しているからです。

例えば、次のコードを考えます。

C#
void GetNumber(out int number)
{
// numberに値を代入していない
}

int result;
GetNumber(out result);
Console.WriteLine(result);

この場合、resultに何が入っているのか分かりません。C#では、未確定の値を使うことを防ぐため、out引数には必ず値を代入するルールになっています。

正しくは次のように書きます。

C#
void GetNumber(out int number)
{
number = 0;
}

条件分岐がある場合も同じです。

C#
bool TryGetNumber(bool canGet, out int number)
{
if (canGet)
{
number = 100;
return true;
}

number = 0;
return false;
}

失敗時でもnumber = 0;のように値を代入しておく必要があります。

3. out引数の具体例

3-1. 数値計算の結果をoutで受け取る例

まずは、2つの数値を足した結果をoutで受け取る例です。

C#
void Add(int a, int b, out int result)
{
result = a + b;
}

int answer;
Add(10, 20, out answer);

Console.WriteLine(answer); // 30

この例では、Addメソッドの中でresultに計算結果を代入しています。呼び出し側では、answerを通してその結果を受け取れます。

ただし、このように結果が1つだけの場合は、通常はreturnを使うほうが自然です。

C#
int Add(int a, int b)
{
return a + b;
}

outを使うのは、戻り値とは別に何かを返したい場合や、複数の結果を返したい場合に向いています。

3-2. 複数の値をoutで返す例

out引数は複数指定できます。

C#
void GetRectangleInfo(int width, int height, out int area, out int perimeter)
{
area = width * height;
perimeter = (width + height) * 2;
}

呼び出し側は次のようになります。

C#
GetRectangleInfo(5, 3, out int area, out int perimeter);

Console.WriteLine(area); // 15
Console.WriteLine(perimeter); // 16

この例では、長方形の面積と周囲の長さを同時に返しています。

通常の戻り値では1つの値しか返せませんが、out引数を使うと複数の値を呼び出し元に渡せます。

3-3. boolの戻り値とoutを組み合わせる例

out引数は、boolの戻り値と組み合わせて使われることが多いです。

C#
bool TryDivide(int a, int b, out int result)
{
if (b == 0)
{
result = 0;
return false;
}

result = a / b;
return true;
}

呼び出し側は次のように書けます。

C#
if (TryDivide(10, 2, out int result))
{
Console.WriteLine($"計算結果: {result}");
}
else
{
Console.WriteLine("割り算に失敗しました");
}

b0の場合、割り算はできません。そのため、戻り値で成功・失敗を返し、成功した場合の計算結果をout引数で返しています。

このように、booloutの組み合わせは、例外を使わずに失敗の可能性がある処理を扱いたいときに便利です。

3-4. TryParseでoutを使う定番パターン

C#でoutを使う代表的な例がTryParseです。

int.Parseは、文字列を整数に変換します。

C#
int number = int.Parse("123");
Console.WriteLine(number); // 123

しかし、変換できない文字列を渡すと例外が発生します。

C#
int number = int.Parse("abc"); // 例外が発生

そこで使うのがint.TryParseです。

C#
bool success = int.TryParse("123", out int number);

if (success)
{
Console.WriteLine($"変換成功: {number}");
}
else
{
Console.WriteLine("変換失敗");
}

TryParseは、変換に成功したかどうかをboolで返します。そして、変換できた値をout引数に入れます。

失敗した場合でも、out引数には既定値が入ります。intの場合は0です。

C#
if (int.TryParse("abc", out int number))
{
Console.WriteLine(number);
}
else
{
Console.WriteLine("数値ではありません");
}

ユーザー入力やファイルの読み込みなど、文字列が本当に数値とは限らない場面では、ParseよりもTryParseを使うほうが安全です。

3-5. DictionaryのTryGetValueでoutを使う例

Dictionary<TKey, TValue>TryGetValueでもoutが使われます。

C#
var scores = new Dictionary<string, int>
{
{ "Taro", 80 },
{ "Hanako", 95 }
};

キーが存在するかどうかを確認しながら値を取得するには、TryGetValueを使います。

C#
if (scores.TryGetValue("Taro", out int score))
{
Console.WriteLine($"点数: {score}");
}
else
{
Console.WriteLine("データが見つかりません");
}

TryGetValueは、キーが存在すればtrueを返し、対応する値をout引数に代入します。キーが存在しなければfalseを返します。

次のように、存在しないキーを指定した場合はelse側が実行されます。

C#
if (scores.TryGetValue("Jiro", out int score))
{
Console.WriteLine(score);
}
else
{
Console.WriteLine("Jiroの点数は登録されていません");
}

ContainsKeyで確認してから値を取得する方法もありますが、TryGetValueを使うと検索と取得をまとめて行えるため、よく使われます。

4. out引数とref引数の違い

4-1. outとrefの役割の違い

outrefは、どちらも引数を通して呼び出し元の変数に影響を与えられるキーワードです。

しかし、役割が異なります。

outは、メソッドの中で値を代入して呼び出し元へ返すために使います。

C#
void SetValue(out int value)
{
value = 100;
}

一方、refは、呼び出し元で用意した値をメソッド内で読み書きするために使います。

C#
void AddTen(ref int value)
{
value += 10;
}

呼び出し側は次のようになります。

C#
int number = 5;
AddTen(ref number);

Console.WriteLine(number); // 15

refでは、もともとnumberに入っていた5をメソッド内で読み取り、その値に10を足しています。

つまり、outは「出力用」、refは「入力と出力の両方に使う」と考えると分かりやすいです。

4-2. 初期化が必要かどうかの違い

outrefの大きな違いの1つが、呼び出し前に変数を初期化する必要があるかどうかです。

outの場合、呼び出し前の初期化は不要です。

C#
int value;
SetValue(out value);

outはメソッド内で値を代入する前提なので、呼び出し前に値が入っていなくても問題ありません。

一方、refの場合は、呼び出し前に必ず初期化が必要です。

C#
int value = 10;
AddTen(ref value);

次のように未初期化のままrefで渡すとエラーになります。

C#
int value;
AddTen(ref value); // エラー

refはメソッド内で渡された値を読む可能性があるため、呼び出し前に値が決まっている必要があります。

4-3. メソッド内で代入が必須かどうかの違い

out引数は、メソッド内で必ず値を代入しなければなりません。

C#
void GetValue(out int value)
{
value = 100;
}

次のように代入しない場合はエラーになります。

C#
void GetValue(out int value)
{
// valueに代入していないのでエラー
}

一方、ref引数は、メソッド内で必ず代入する必要はありません。

C#
void ShowValue(ref int value)
{
Console.WriteLine(value);
}

refは呼び出し前にすでに値が入っているため、メソッド内で代入しなくても問題ありません。

ただし、refを使う以上、メソッド内で値を変更できるため、意図しない変更に注意が必要です。

4-4. outとrefの使い分け

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

メソッド内で新しく値を作って返したい場合はoutを使います。

C#
bool TryCreateUserName(out string userName)
{
userName = "guest";
return true;
}

呼び出し元の値をメソッド内で加工したい場合はrefを使います。

C#
void Increment(ref int count)
{
count++;
}

ただし、refは呼び出し元の変数を直接変更するため、コードの流れが分かりにくくなることがあります。必要な場面以外では、戻り値を使うほうが読みやすい場合も多いです。

C#
int Increment(int count)
{
return count + 1;
}

初心者のうちは、次のように覚えるとよいでしょう。

outは結果を受け取るための引数です。refは既存の値を変更するための引数です。

4-5. outとrefの違いを比較表で整理

項目outref
主な役割メソッドから値を返す渡した値をメソッド内で読み書きする
呼び出し前の初期化不要必要
メソッド内での代入必須必須ではない
呼び出し側の書き方out 変数ref 変数
よく使う場面TryParseTryGetValue、複数結果の返却値の更新、入れ替え、加工
注意点代入忘れでエラーになる意図しない値変更に注意

例えば、次のように未初期化の変数を渡せるのはoutです。

C#
int number;
GetNumber(out number);

一方、refでは初期化が必要です。

C#
int number = 10;
UpdateNumber(ref number);

どちらも似ていますが、目的が異なるため、使い分けを意識することが大切です。

5. out引数で複数戻り値を扱う方法

5-1. C#で複数の値を返したい場面

C#でメソッドから複数の値を返したい場面はよくあります。

例えば、次のようなケースです。

  • 計算結果と余りを返したい

  • ユーザー情報として名前と年齢を返したい

  • 処理の成功・失敗と結果を返したい

  • 最大値と最小値を同時に返したい

  • 検索結果とメッセージを返したい

通常のreturnでは、基本的に1つの値しか返せません。

C#
int GetScore()
{
return 80;
}

複数の値を返したい場合、out引数を使うと、複数の変数に結果を入れて呼び出し元へ渡せます。

5-2. out引数を複数指定する書き方

out引数は、1つのメソッドに複数指定できます。

C#
void GetValues(out int a, out int b, out int c)
{
a = 1;
b = 2;
c = 3;
}

呼び出し側では、それぞれの引数にoutを付けます。

C#
GetValues(out int x, out int y, out int z);

Console.WriteLine(x); // 1
Console.WriteLine(y); // 2
Console.WriteLine(z); // 3

既存の変数を使うこともできます。

C#
int x;
int y;
int z;

GetValues(out x, out y, out z);

out varを使えば、型を省略できます。

C#
GetValues(out var x, out var y, out var z);

ただし、out引数が多すぎると、何の値を受け取っているのか分かりにくくなります。2〜3個程度なら分かりやすいですが、それ以上になる場合は別の方法も検討しましょう。

5-3. out引数で複数戻り値を返すサンプルコード

次は、配列の最大値と最小値をout引数で返す例です。

C#
bool TryGetMinMax(int[] numbers, out int min, out int max)
{
if (numbers == null || numbers.Length == 0)
{
min = 0;
max = 0;
return false;
}

min = numbers[0];
max = numbers[0];

foreach (int number in numbers)
{
if (number < min)
{
min = number;
}

if (number > max)
{
max = number;
}
}

return true;
}

呼び出し側は次のように書けます。

C#
int[] scores = { 80, 65, 90, 72 };

if (TryGetMinMax(scores, out int min, out int max))
{
Console.WriteLine($"最小値: {min}");
Console.WriteLine($"最大値: {max}");
}
else
{
Console.WriteLine("データがありません");
}

この例では、戻り値のboolで取得に成功したかどうかを返し、out引数でminmaxを返しています。

配列がnullまたは空の場合は、最小値と最大値を求められないため、falseを返しています。このときも、out引数には必ず値を代入しています。

5-4. out引数が増えすぎた場合の注意点

out引数は便利ですが、増えすぎるとコードの可読性が下がります。

例えば、次のようなメソッドは分かりにくくなりがちです。

C#
void GetUserInfo(
out int id,
out string name,
out int age,
out string email,
out string address)
{
id = 1;
name = "Taro";
age = 25;
email = "taro@example.com";
address = "Tokyo";
}

呼び出し側も長くなります。

C#
GetUserInfo(
out int id,
out string name,
out int age,
out string email,
out string address);

このようにout引数が多い場合、どの値が何を表しているのか分かりにくくなります。また、引数の順番を間違えるとバグの原因になります。

複数の値が1つのまとまった情報を表すなら、クラスや構造体、またはタプルを使うほうが適しています。

5-5. Tuple・ValueTupleとの違い

C#では、複数の値を返す方法としてTupleValueTupleも使えます。

特にValueTupleを使うと、次のように書けます。

C#
(int min, int max) GetMinMax(int[] numbers)
{
int min = numbers[0];
int max = numbers[0];

foreach (int number in numbers)
{
if (number < min)
{
min = number;
}

if (number > max)
{
max = number;
}
}

return (min, max);
}

呼び出し側は次のようになります。

C#
var result = GetMinMax(new[] { 80, 65, 90 });

Console.WriteLine(result.min); // 65
Console.WriteLine(result.max); // 90

分解して受け取ることもできます。

C#
var (min, max) = GetMinMax(new[] { 80, 65, 90 });

out引数との違いは、戻り値として複数の値をまとめて返せる点です。

outは、TryParseのように成功・失敗をboolで返し、結果を別で受け取りたい場合に向いています。一方、単純に複数の値を返したいだけなら、ValueTupleのほうが読みやすい場合があります。

5-6. クラスや構造体で返す方法との違い

複数の値が意味のあるまとまりになっている場合は、クラスや構造体で返す方法もあります。

例えば、ユーザー情報を返す場合です。

C#
class UserInfo
{
public int Id { get; set; }
public string Name { get; set; } = "";
public int Age { get; set; }
}

メソッドは次のように書けます。

C#
UserInfo GetUserInfo()
{
return new UserInfo
{
Id = 1,
Name = "Taro",
Age = 25
};
}

呼び出し側は次のようになります。

C#
UserInfo user = GetUserInfo();

Console.WriteLine(user.Name);
Console.WriteLine(user.Age);

この方法は、戻り値の意味が明確になりやすいというメリットがあります。

out引数は一時的な結果や、成功・失敗と結果を分けたい場合に便利です。一方、返したい情報が業務上の意味を持つデータであれば、クラスや構造体を使うほうが保守しやすくなります。

6. out引数のメリット・デメリット

6-1. out引数を使うメリット

out引数のメリットは、戻り値とは別に値を返せることです。

例えば、成功・失敗を戻り値で返し、実際の結果をoutで受け取る書き方は分かりやすいです。

C#
if (int.TryParse("123", out int number))
{
Console.WriteLine(number);
}

このコードを見ると、「変換できた場合だけnumberを使う」という流れが自然に分かります。

また、例外を使わずに失敗を扱える点もメリットです。ユーザー入力のように失敗が普通に起こる処理では、TryParseのようなパターンが適しています。

さらに、複数の結果を簡単に返せる点も便利です。

C#
GetSize(out int width, out int height);

短いメソッドや限定的な処理であれば、out引数はシンプルで実用的です。

6-2. out引数を使うデメリット

一方で、out引数にはデメリットもあります。

まず、メソッドの戻り値だけを見ても、実際に何が返ってくるのか分かりにくい場合があります。

C#
bool TryGetData(out string name, out int age, out string email)

このようにout引数が増えると、メソッドの呼び出し側が複雑になります。

また、out引数はメソッド内で必ず代入する必要があるため、条件分岐が多い処理では代入漏れに注意しなければなりません。

さらに、outは呼び出し元の変数に値を入れる仕組みなので、戻り値だけで完結するメソッドに比べると、データの流れが少し追いにくくなることがあります。

6-3. 可読性が下がりやすいケース

out引数で可読性が下がりやすいのは、主に次のようなケースです。

1つ目は、out引数が多すぎる場合です。

C#
GetData(out int id, out string name, out int age, out string email, out string address);

このようなコードは、引数の順番や意味を把握しづらくなります。

2つ目は、メソッド名からout引数の意味が分かりにくい場合です。

C#
Process(out int result);

Processという名前だけでは、何を処理して何の結果を返すのか分かりません。

3つ目は、戻り値とout引数の関係が分かりにくい場合です。

C#
int Execute(out string message);

この場合、戻り値のintが何を表し、messageが何を表すのか、呼び出し側から判断しづらくなります。

outを使う場合は、メソッド名や変数名を分かりやすくすることが重要です。

6-4. テストや保守で注意すべき点

out引数を使うメソッドをテストする場合、戻り値だけでなくout引数に設定された値も確認する必要があります。

C#
bool success = TryGetNumber("123", out int number);

Console.WriteLine(success); // true
Console.WriteLine(number); // 123

テストでは、成功時だけでなく失敗時のout引数の値も確認しておくと安心です。

C#
bool success = TryGetNumber("abc", out int number);

Console.WriteLine(success); // false
Console.WriteLine(number); // 0など

また、保守の面では、後からout引数を追加すると呼び出し側の修正が必要になります。

C#
TryGetUser(out string name);

このメソッドを次のように変更するとします。

C#
TryGetUser(out string name, out int age);

すると、既存の呼び出しコードをすべて修正しなければなりません。

そのため、返す値が増えそうな処理では、最初から専用の戻り値クラスを用意することも検討しましょう。

6-5. out引数を避けたほうがよいケース

次のような場合は、out引数を避けたほうがよいことがあります。

単純に1つの値を返すだけなら、returnを使うほうが自然です。

C#
int GetTotal()
{
return 100;
}

複数の値が1つのデータとしてまとまっているなら、クラスや構造体を使うほうが分かりやすいです。

C#
UserInfo GetUserInfo()
{
return new UserInfo();
}

一時的に複数の値を返したいだけなら、ValueTupleも選択肢になります。

C#
(string name, int age) GetUser()
{
return ("Taro", 25);
}

out引数は便利ですが、何でもoutで返せばよいわけではありません。コードの読みやすさや保守性を考えて選ぶことが大切です。

7. out引数でよくあるエラーと対処法

7-1. out引数に値を代入していないエラー

out引数で最もよくあるエラーは、メソッド内で値を代入していないケースです。

C#
void GetNumber(out int number)
{
// numberに値を代入していない
}

このコードはコンパイルエラーになります。out引数は、メソッドが終了するまでに必ず値を代入する必要があるためです。

正しくは次のように書きます。

C#
void GetNumber(out int number)
{
number = 100;
}

条件分岐がある場合も注意が必要です。

C#
bool TryGetNumber(bool success, out int number)
{
if (success)
{
number = 100;
return true;
}

// numberに代入していないのでエラー
return false;
}

失敗時にも値を代入しましょう。

C#
bool TryGetNumber(bool success, out int number)
{
if (success)
{
number = 100;
return true;
}

number = 0;
return false;
}

7-2. 呼び出し時にoutを書き忘れるエラー

out引数を使う場合、呼び出し側にもoutを書く必要があります。

C#
void GetNumber(out int number)
{
number = 100;
}

次の呼び出しはエラーになります。

C#
int result;
GetNumber(result); // エラー

正しくは次のように書きます。

C#
int result;
GetNumber(out result);

outは、メソッド定義側だけでなく、呼び出し側にも必要です。これにより、呼び出し側のコードを見たときに「この変数はメソッドによって値が設定される」と分かるようになっています。

7-3. refとoutを混同したときのエラー

refoutを混同するとエラーになります。

例えば、メソッド定義がoutなのに、呼び出し側でrefを使うことはできません。

C#
void GetNumber(out int number)
{
number = 100;
}

int result;
GetNumber(ref result); // エラー

正しくは次のように書きます。

C#
GetNumber(out result);

逆に、メソッド定義がrefの場合は、呼び出し側もrefにする必要があります。

C#
void AddTen(ref int number)
{
number += 10;
}

int value = 5;
AddTen(ref value);

また、refでは呼び出し前の初期化が必要です。

C#
int value;
AddTen(ref value); // エラー

正しくは次のように初期化します。

C#
int value = 0;
AddTen(ref value);

7-4. out引数にプロパティを渡せないケース

out引数には、通常の変数を渡します。プロパティはout引数として渡せない場合があります。

例えば、次のようなクラスがあるとします。

C#
class User
{
public int Age { get; set; }
}

次のようにプロパティをout引数に渡すことはできません。

C#
void GetAge(out int age)
{
age = 20;
}

User user = new User();

GetAge(out user.Age); // エラー

この場合は、一度ローカル変数で受け取ってからプロパティに代入します。

C#
GetAge(out int age);
user.Age = age;

なぜなら、out引数は変数そのものを書き換える仕組みであり、プロパティは実際にはメソッドのように動作する場合があるためです。

7-5. nullや型変換で起きやすいトラブル

out引数では、型の扱いにも注意が必要です。

例えば、int.TryParseでは、out引数にはint型の変数を渡す必要があります。

C#
int.TryParse("123", out int number);

次のように型が違う変数は渡せません。

C#
string text;
int.TryParse("123", out text); // エラー

また、参照型をoutで返す場合は、nullが入る可能性にも注意しましょう。

C#
bool TryGetName(out string? name)
{
name = null;
return false;
}

このようなメソッドでは、呼び出し側で成功したかどうかを確認してから値を使う必要があります。

C#
if (TryGetName(out string? name))
{
Console.WriteLine(name.Length);
}
else
{
Console.WriteLine("名前を取得できませんでした");
}

out引数を使うときは、成功時と失敗時でどのような値が入るのかを明確にしておくことが大切です。

8. out引数を使うべき場面・使わないほうがよい場面

8-1. TryParse系メソッドで使う場面

out引数を使うべき代表的な場面は、TryParse系メソッドです。

C#
if (int.TryParse("123", out int number))
{
Console.WriteLine(number);
}

TryParseは、変換に成功したかどうかをboolで返し、変換結果をout引数で返します。

このパターンは、失敗する可能性がある処理に向いています。

例えば、ユーザーが入力した文字列を数値に変換する場合です。

C#
Console.WriteLine("年齢を入力してください:");
string? input = Console.ReadLine();

if (int.TryParse(input, out int age))
{
Console.WriteLine($"年齢: {age}");
}
else
{
Console.WriteLine("正しい数値を入力してください");
}

ユーザー入力は必ず正しいとは限らないため、ParseよりもTryParseのほうが安全です。

8-2. 処理の成功・失敗と結果を同時に返したい場面

out引数は、処理の成功・失敗と結果を同時に返したい場合に向いています。

C#
bool TryFindUser(int id, out string name)
{
if (id == 1)
{
name = "Taro";
return true;
}

name = "";
return false;
}

呼び出し側は次のようになります。

C#
if (TryFindUser(1, out string name))
{
Console.WriteLine($"ユーザー名: {name}");
}
else
{
Console.WriteLine("ユーザーが見つかりません");
}

このように書くと、処理に成功した場合だけ結果を使う流れが分かりやすくなります。

Dictionary.TryGetValueも同じ考え方です。

C#
if (dictionary.TryGetValue("key", out string? value))
{
Console.WriteLine(value);
}

戻り値のboolで成功・失敗を判断し、out引数で取得結果を受け取るパターンは、C#でよく使われます。

8-3. 戻り値が複雑になる場合の判断基準

戻り値が複雑になる場合、outを使うべきかどうかは慎重に判断する必要があります。

例えば、次のようなメソッドは、少し複雑です。

C#
bool TryGetUser(
int id,
out string name,
out int age,
out string email)

この程度であればまだ理解できますが、さらにout引数が増えると読みにくくなります。

判断基準としては、次のように考えるとよいでしょう。

out引数が1〜2個で、成功・失敗と結果を返したい場合はoutが向いています。

C#
bool TryGetScore(string name, out int score)

返す値が3個以上ある場合や、それらが1つのデータとしてまとまっている場合は、クラスや構造体を検討しましょう。

C#
UserInfo? GetUserInfo(int id)

一時的な計算結果を複数返したい場合は、ValueTupleも便利です。

C#
(int min, int max) GetMinMax(int[] numbers)

8-4. Tupleや専用クラスを選ぶべき場面

TupleValueTupleを選ぶべき場面は、複数の値を戻り値として自然に返したい場合です。

C#
(string name, int age) GetUser()
{
return ("Taro", 25);
}

呼び出し側も分かりやすく書けます。

C#
var (name, age) = GetUser();

ただし、業務上意味のあるデータを返す場合は、専用クラスを作るほうがよいです。

C#
class User
{
public string Name { get; set; } = "";
public int Age { get; set; }
}
C#
User GetUser()
{
return new User
{
Name = "Taro",
Age = 25
};
}

専用クラスを使うと、後からプロパティを追加しやすく、コードの意味も明確になります。

一方、outは、TryParseTryGetValueのように、成功・失敗を戻り値で返し、結果を別で受け取りたい場合に向いています。

8-5. 初心者が覚えておきたい使い分け

初心者がまず覚えておきたい使い分けは、次のとおりです。

1つの値を返すだけならreturnを使います。

C#
int GetTotal()
{
return 100;
}

成功・失敗と結果を返したいならoutを使います。

C#
bool TryGetTotal(out int total)
{
total = 100;
return true;
}

複数の値を手軽に返したいならValueTupleを使います。

C#
(int width, int height) GetSize()
{
return (100, 200);
}

まとまったデータを返したいならクラスや構造体を使います。

C#
SizeInfo GetSizeInfo()
{
return new SizeInfo();
}

outは便利ですが、すべての場面で使うものではありません。特に初心者のうちは、TryParseTryGetValueの使い方を理解するところから始めるのがおすすめです。

9. C#のout引数に関するよくある質問

9-1. out引数はなぜ必要なのか

out引数は、戻り値とは別に値を返したいときに必要です。

特に、処理の成功・失敗を戻り値で返し、実際の結果を別で返したい場合に役立ちます。

C#
if (int.TryParse("123", out int number))
{
Console.WriteLine(number);
}

この例では、変換に成功したかどうかをboolで受け取り、変換結果をnumberで受け取っています。

もしoutがなければ、成功・失敗と結果を同時に扱うために、例外処理や専用クラスなど別の方法が必要になります。

9-2. out引数は戻り値と何が違うのか

戻り値は、returnで返す値です。

C#
int GetNumber()
{
return 100;
}

out引数は、引数を通して値を返します。

C#
void GetNumber(out int number)
{
number = 100;
}

単純に1つの値を返すだけなら、戻り値を使うほうが自然です。

out引数は、戻り値とは別の値を返したい場合や、複数の値を返したい場合に使います。

9-3. out引数とref引数はどちらを使うべきか

新しく値を作って呼び出し元に返したい場合はoutを使います。

C#
void GetValue(out int value)
{
value = 100;
}

呼び出し元の値をメソッド内で読み取り、さらに変更したい場合はrefを使います。

C#
void AddTen(ref int value)
{
value += 10;
}

ただし、refは値の流れが分かりにくくなることがあるため、必要な場合に限定して使うのが基本です。

迷った場合は、次のように考えるとよいでしょう。

結果を受け取るだけならout、元の値を変更するならrefです。

9-4. out引数で複数の値を返すのはよくないのか

out引数で複数の値を返すこと自体は悪くありません。

例えば、次のようなコードは分かりやすいです。

C#
bool TryGetMinMax(int[] numbers, out int min, out int max)

成功・失敗を返しながら、最小値と最大値を受け取れるため、自然な使い方です。

ただし、out引数が多すぎる場合は注意が必要です。

C#
GetUser(out int id, out string name, out int age, out string email, out string address);

このような場合は、クラスや構造体で返すほうが読みやすくなります。

C#
UserInfo GetUser()

out引数は2〜3個程度までに抑え、それ以上になる場合は別の方法を検討するとよいでしょう。

9-5. out varとは何か

out varとは、out引数を使うときに、その場で変数を宣言し、型をコンパイラに推論させる書き方です。

C#
if (int.TryParse("123", out var number))
{
Console.WriteLine(number);
}

この場合、numberint型として扱われます。なぜなら、int.TryParseout引数がint型だからです。

型を明示して書くこともできます。

C#
if (int.TryParse("123", out int number))
{
Console.WriteLine(number);
}

out varを使うとコードが短くなります。ただし、初心者のうちは型を意識するためにout int numberのように書くと理解しやすいです。

9-6. out引数は実務でよく使われるのか

out引数は実務でも使われます。

特によく見かけるのは、次のようなメソッドです。

C#
int.TryParse
double.TryParse
DateTime.TryParse
Dictionary.TryGetValue

これらはC#の標準的なAPIでもよく使われるパターンです。

一方で、自分で新しく作るメソッドでは、何でもoutを使うわけではありません。

単純な戻り値ならreturn、複数の値を返すならValueTuple、意味のあるデータならクラスや構造体を使うことも多いです。

つまり、out引数は実務で使われますが、主に「成功・失敗と結果を同時に返す場面」で使うと覚えておくとよいでしょう。

まとめ

C#のout引数は、メソッドの中で値を代入し、その値を呼び出し元に返すための仕組みです。

通常の戻り値はreturnで1つの値を返しますが、out引数を使うと、戻り値とは別に値を返したり、複数の値を返したりできます。

特に代表的なのが、TryParseTryGetValueのような使い方です。

C#
if (int.TryParse("123", out int number))
{
Console.WriteLine(number);
}

このパターンでは、戻り値のboolで成功・失敗を判断し、out引数で実際の結果を受け取ります。

また、outと似たキーワードにrefがありますが、役割は異なります。outはメソッド内で値を設定して返すためのもの、refは呼び出し元の値をメソッド内で読み書きするためのものです。

out引数を使うときは、次のポイントを押さえておきましょう。

  • メソッド定義側と呼び出し側の両方にoutを書く

  • out引数にはメソッド内で必ず値を代入する

  • 呼び出し前の初期化は不要

  • 成功・失敗と結果を同時に返す場面に向いている

  • out引数が多すぎる場合は、ValueTupleやクラスも検討する

out引数は、C#の基本文法の中でも実務でよく登場する重要な機能です。まずはint.TryParseDictionary.TryGetValueの使い方を理解し、必要に応じて自分のメソッドでも活用していきましょう。