C#のoutとは?使い方・refとの違い・複数戻り値の実装例を初心者向けに解説
はじめに
C#を学び始めると、メソッドの引数に付くoutというキーワードを見かけることがあります。たとえば、文字列を数値に変換するint.TryParseでは、次のようにoutが使われます。
C#int.TryParse("123", out int number);
一見すると「なぜ戻り値があるのに、さらにoutで値を受け取るの?」と疑問に思うかもしれません。
C#のoutは、メソッドから値を受け取るための仕組みです。通常、メソッドのreturnで返せる値は1つですが、outを使うと、メソッドの呼び出し元へ追加の値を返すことができます。
この記事では、C#のoutとは何か、基本的な使い方、refとの違い、複数戻り値の実装例、実務でよく使われるパターンまで、初心者にもわかりやすく解説します。
1. C#のoutとは?初心者向けにわかりやすく解説
1-1. outキーワードの基本的な意味
C#のoutとは、メソッドの中で作成した値を、呼び出し元の変数に代入して返すためのキーワードです。
通常、メソッドから値を返すときはreturnを使います。
C#int Add(int a, int b)
{
return a + b;
}
この場合、返せる値はa + bの1つだけです。
一方、outを使うと、returnとは別に値を呼び出し元へ渡すことができます。
C#void GetValues(out int x, out int y)
{
x = 10;
y = 20;
}
このメソッドを呼び出すと、xとyの2つの値を受け取れます。
C#GetValues(out int a, out int b);
Console.WriteLine(a); // 10
Console.WriteLine(b); // 20
つまり、outは「メソッドから外へ値を出す」ためのキーワードです。
1-2. outを使うと何ができるのか
outを使うと、主に次のようなことができます。
メソッドから複数の値を返すことができます。たとえば、計算結果と処理が成功したかどうかを同時に返すような場面で便利です。
また、TryParseのように、失敗する可能性がある処理でも例外を使わずに安全に結果を受け取れます。
C#bool success = int.TryParse("100", out int result);
if (success)
{
Console.WriteLine(result);
}
この例では、TryParseの戻り値で変換に成功したかどうかを受け取り、out引数で変換後の数値を受け取っています。
1-3. outが使われる代表的な場面
C#のoutは、次のような場面でよく使われます。
文字列を数値に変換するint.TryParseでは、変換できたかどうかをboolで返し、変換結果をoutで返します。
C#bool success = int.TryParse("123", out int number);
Dictionaryからキーに対応する値を取得するTryGetValueでも、値の取得に成功したかどうかを戻り値で返し、取得した値をoutで返します。
C#var scores = new Dictionary<string, int>
{
{ "Alice", 90 },
{ "Bob", 80 }
};
bool found = scores.TryGetValue("Alice", out int score);
また、自分で作成するメソッドでも、処理の成功可否と結果を同時に返したいときにoutを使えます。
1-4. outを理解する前に知っておきたい「値渡し」と「参照渡し」
outを理解するには、C#の引数の渡し方を知っておくと理解しやすくなります。
通常の引数は「値渡し」です。値渡しでは、メソッドに値のコピーが渡されます。
C#void ChangeValue(int x)
{
x = 100;
}
int number = 10;
ChangeValue(number);
Console.WriteLine(number); // 10
この例では、メソッド内でxを100に変更しても、呼び出し元のnumberは変わりません。
一方、outを使うと、メソッド内で代入した値が呼び出し元の変数に反映されます。
C#void SetValue(out int x)
{
x = 100;
}
SetValue(out int number);
Console.WriteLine(number); // 100
outは、メソッド側から呼び出し元へ値を渡すための仕組みだと考えるとわかりやすいです。
2. C#のoutの基本構文と使い方
2-1. out引数を使うメソッドの書き方
out引数を使うメソッドは、引数の型の前にoutを付けて定義します。
C#void メソッド名(out 型 変数名)
{
変数名 = 値;
}
たとえば、整数値を返すout引数を持つメソッドは次のように書きます。
C#void GetNumber(out int number)
{
number = 10;
}
このメソッドでは、numberに10を代入しています。out引数は、メソッド内で必ず値を代入する必要があります。
2-2. out引数を呼び出す側の書き方
out引数を持つメソッドを呼び出すときも、引数の前にoutを付けます。
C#int value;
GetNumber(out value);
Console.WriteLine(value); // 10
メソッド定義側だけでなく、呼び出し側にもoutを書く必要があります。
これは、通常の引数とは違い、その変数がメソッドによって変更されることを明確にするためです。
2-3. out変数宣言の使い方
C#では、メソッド呼び出し時にout変数をその場で宣言できます。
C#GetNumber(out int value);
Console.WriteLine(value);
この書き方を「out変数宣言」と呼びます。
従来は、先に変数を宣言してからメソッドに渡す必要がありました。
C#int value;
GetNumber(out value);
しかし、現在のC#では次のように1行で書けます。
C#GetNumber(out int value);
特にTryParseでは、out変数宣言がよく使われます。
C#if (int.TryParse("123", out int number))
{
Console.WriteLine(number);
}
この書き方により、変数の宣言場所が近くなり、コードが読みやすくなります。
2-4. out引数にはメソッド内で必ず値を代入する必要がある
out引数を使う場合、メソッド内で必ず値を代入しなければなりません。
次のコードはコンパイルエラーになります。
C#void GetNumber(out int number)
{
// numberに値を代入していない
}
outは「メソッドから値を返す」ための引数です。そのため、メソッドが終了するまでに必ず値が入っている必要があります。
条件分岐がある場合も注意が必要です。
C#void GetNumber(bool flag, out int number)
{
if (flag)
{
number = 10;
}
}
このコードも、flagがfalseの場合にnumberへ値が代入されないため、コンパイルエラーになります。
正しくは、すべての処理経路で値を代入します。
C#void GetNumber(bool flag, out int number)
{
if (flag)
{
number = 10;
}
else
{
number = 0;
}
}
2-5. outを使ったシンプルなサンプルコード
次の例では、outを使って名前と年齢をメソッドから返しています。
C#void GetUserInfo(out string name, out int age)
{
name = "Taro";
age = 25;
}
GetUserInfo(out string userName, out int userAge);
Console.WriteLine(userName); // Taro
Console.WriteLine(userAge); // 25
このように、outを使うと、1つのメソッドから複数の情報を受け取れます。
ただし、outを使いすぎるとメソッドの意味がわかりにくくなることがあります。シンプルな用途に絞って使うのがポイントです。
3. outを使って複数の戻り値を実装する方法
3-1. 通常のreturnでは1つの値しか返せない
C#のreturnで返せる値は基本的に1つです。
C#int Add(int a, int b)
{
return a + b;
}
このメソッドは、足し算の結果だけを返します。
しかし、実際のプログラムでは、複数の値を返したい場面があります。
たとえば、割り算の結果と余りを同時に返したい場合です。
C#10 ÷ 3 = 商 3、余り 1
このようなとき、outを使うと複数の値を呼び出し元に返せます。
3-2. outを使って複数の値を返すサンプル
次の例では、割り算の商と余りをoutで返します。
C#void Divide(int a, int b, out int quotient, out int remainder)
{
quotient = a / b;
remainder = a % b;
}
Divide(10, 3, out int q, out int r);
Console.WriteLine(q); // 3
Console.WriteLine(r); // 1
このメソッドでは、戻り値の型はvoidですが、out引数を使ってquotientとremainderの2つの値を返しています。
returnを使わなくても、outによって呼び出し元で結果を受け取れる点が特徴です。
3-3. 計算結果と判定結果を同時に返す例
outは、処理が成功したかどうかと、処理結果を同時に返す場面でよく使われます。
次の例では、割り算ができたかどうかを戻り値で返し、計算結果をoutで返します。
C#bool TryDivide(int a, int b, out double result)
{
if (b == 0)
{
result = 0;
return false;
}
result = (double)a / b;
return true;
}
呼び出し側は次のように書けます。
C#if (TryDivide(10, 2, out double answer))
{
Console.WriteLine(answer); // 5
}
else
{
Console.WriteLine("割り算できません");
}
この書き方では、戻り値のboolで成功可否を判断し、成功した場合だけoutで受け取った結果を使えます。
これはC#でよく使われる「Tryパターン」です。
3-4. outを使うメリットと注意点
outを使うメリットは、複数の値をシンプルに返せることです。
特に、処理の成功可否をboolで返し、結果をoutで受け取る形は、C#の標準ライブラリでもよく使われています。
一方で、注意点もあります。
out引数が多くなると、メソッドの呼び出しが読みにくくなります。
C#GetData(out int id, out string name, out int age, out string address);
このようにoutが多いと、何を返しているメソッドなのか理解しにくくなります。
また、out引数は呼び出し元の変数に値を代入するため、処理の流れを追いにくくなることもあります。
3-5. 複数戻り値ならタプルやクラスとの使い分けも重要
C#では、複数の値を返す方法はoutだけではありません。
タプルを使うと、複数の値をまとめて返せます。
C#(int quotient, int remainder) Divide(int a, int b)
{
return (a / b, a % b);
}
var result = Divide(10, 3);
Console.WriteLine(result.quotient); // 3
Console.WriteLine(result.remainder); // 1
また、意味のあるデータとしてまとめたい場合は、クラスや構造体を使う方法もあります。
C#class UserInfo
{
public string Name { get; set; }
public int Age { get; set; }
}
一時的に複数の値を返すだけならoutやタプルが便利です。
一方、データに意味があり、今後も使い回す場合は、クラスや構造体として定義したほうが保守しやすくなります。
4. C#のoutとrefの違い
4-1. outとrefの共通点
outとよく比較されるキーワードにrefがあります。
outとrefは、どちらもメソッド内で変更した値を呼び出し元に反映できるという共通点があります。
C#void SetValue(out int x)
{
x = 100;
}
void ChangeValue(ref int x)
{
x = 100;
}
どちらも、メソッドに渡した変数の値をメソッド側で変更できます。
そのため、初心者にとっては似た機能に見えるかもしれません。
4-2. outとrefの最大の違いは「事前代入が必要かどうか」
outとrefの最大の違いは、呼び出し前に変数へ値を入れておく必要があるかどうかです。
outは、呼び出し前に変数を初期化していなくても使えます。
C#int number;
GetNumber(out number);
一方、refは呼び出し前に必ず変数へ値を入れておく必要があります。
C#int number = 10;
ChangeNumber(ref number);
outは「メソッドから値を受け取る」目的で使われます。
refは「すでにある値をメソッド内で変更する」目的で使われます。
4-3. outはメソッド内で必ず代入が必要
out引数は、メソッド内で必ず値を代入する必要があります。
C#void GetNumber(out int number)
{
number = 100;
}
次のように、値を代入しないコードはエラーになります。
C#void GetNumber(out int number)
{
// 代入していないためエラー
}
これは、outが「未初期化の変数にメソッド側で値を入れて返す」仕組みだからです。
4-4. refは呼び出し前に変数へ値を入れておく必要がある
refを使う場合、呼び出し前に変数へ値を代入しておく必要があります。
C#void AddTen(ref int number)
{
number += 10;
}
int value = 5;
AddTen(ref value);
Console.WriteLine(value); // 15
もし、未初期化の変数をrefに渡そうとするとコンパイルエラーになります。
C#int value;
AddTen(ref value); // エラー
refは、すでにある値をメソッドに渡し、その値を変更して戻すためのものです。
4-5. outとrefの違いをコードで比較
outとrefの違いをコードで比較してみましょう。
まずはoutです。
C#void SetNumber(out int number)
{
number = 100;
}
int a;
SetNumber(out a);
Console.WriteLine(a); // 100
outでは、呼び出し前にaへ値を代入していなくても問題ありません。
次にrefです。
C#void UpdateNumber(ref int number)
{
number += 100;
}
int b = 50;
UpdateNumber(ref b);
Console.WriteLine(b); // 150
refでは、呼び出し前にbへ値を代入しておく必要があります。
違いをまとめると、outはメソッドから新しく値を受け取るとき、refは既存の値を変更するときに使います。
4-6. outとrefはどちらを使うべきか
基本的には、目的に合わせて使い分けます。
メソッドから結果を受け取りたい場合はoutを使います。
C#bool success = int.TryParse("123", out int number);
すでにある値をメソッド内で変更したい場合はrefを使います。
C#void Increment(ref int number)
{
number++;
}
ただし、refは値がどこで変更されるのか追いにくくなるため、使いすぎには注意が必要です。
初心者の場合は、まずoutをTryParseやTryGetValueの使い方と一緒に覚えるのがおすすめです。
5. outがよく使われる実践例
5-1. int.TryParseで文字列を数値に変換する例
C#でoutがもっともよく使われる例の1つがint.TryParseです。
int.Parseを使うと、変換できない文字列を渡したときに例外が発生します。
C#int number = int.Parse("abc"); // 例外が発生する
一方、int.TryParseを使うと、変換に成功したかどうかをboolで受け取れます。
C#bool success = int.TryParse("123", out int number);
if (success)
{
Console.WriteLine(number);
}
else
{
Console.WriteLine("数値に変換できません");
}
successがtrueなら変換成功、falseなら変換失敗です。
変換に成功した場合、numberには変換後の整数が入ります。
5-2. Dictionary.TryGetValueで値を安全に取得する例
Dictionaryから値を取得するときにも、outがよく使われます。
通常、存在しないキーで値を取得しようとするとエラーになることがあります。
C#var scores = new Dictionary<string, int>
{
{ "Alice", 90 },
{ "Bob", 80 }
};
int score = scores["Charlie"]; // キーが存在しないためエラー
安全に値を取得したい場合は、TryGetValueを使います。
C#if (scores.TryGetValue("Alice", out int score))
{
Console.WriteLine(score);
}
else
{
Console.WriteLine("指定したキーは存在しません");
}
TryGetValueは、キーが存在すればtrueを返し、値をout引数に代入します。
キーが存在しない場合はfalseを返すため、例外を避けて安全に処理できます。
5-3. 独自メソッドで処理の成功可否と結果を返す例
自分で作るメソッドでも、outを使って成功可否と結果を返せます。
次の例では、文字列を2倍した数値に変換するメソッドを作っています。
C#bool TryDouble(string text, out int result)
{
if (int.TryParse(text, out int number))
{
result = number * 2;
return true;
}
result = 0;
return false;
}
呼び出し側は次のように書けます。
C#if (TryDouble("50", out int value))
{
Console.WriteLine(value); // 100
}
else
{
Console.WriteLine("処理に失敗しました");
}
このように、outを使うと、失敗する可能性がある処理をわかりやすく書けます。
5-4. エラーを避けるためのTryパターン
C#では、失敗する可能性がある処理に対してTry〇〇という名前のメソッドがよく使われます。
代表例は次のようなメソッドです。
C#int.TryParse
double.TryParse
DateTime.TryParse
Dictionary.TryGetValue
これらのメソッドは、基本的に次のような形になっています。
C#bool TrySomething(入力値, out 結果)
戻り値のboolで成功か失敗かを表し、out引数で結果を返します。
このパターンを使うと、例外処理を多用せずに、安全で読みやすいコードを書けます。
5-5. 実務でoutを使うケース
実務では、outは次のような場面で使われます。
ユーザー入力の文字列を数値や日付に変換する場合、TryParseで変換できるかを確認します。
C#if (DateTime.TryParse(input, out DateTime date))
{
Console.WriteLine(date);
}
設定値やキャッシュから値を取得する場合、TryGetValueのような形式で値の有無を確認します。
C#if (cache.TryGetValue("userId", out int userId))
{
Console.WriteLine(userId);
}
また、独自の検索処理や変換処理でも、成功可否と結果を同時に返したい場合にoutが役立ちます。
C#bool TryFindUser(int id, out string userName)
{
if (id == 1)
{
userName = "Taro";
return true;
}
userName = "";
return false;
}
このように、outは「処理が成功したか」と「結果の値」を分けて扱いたいときに便利です。
6. outを使うときの注意点とよくあるエラー
6-1. out引数に値を代入しないとコンパイルエラーになる
out引数を使うときにもっとも多いミスは、メソッド内で値を代入し忘れることです。
C#void GetMessage(out string message)
{
// messageに値を代入していない
}
このコードはコンパイルエラーになります。
正しくは、必ず値を代入します。
C#void GetMessage(out string message)
{
message = "Hello";
}
条件分岐がある場合も、すべての分岐で値を代入する必要があります。
C#void GetMessage(bool success, out string message)
{
if (success)
{
message = "成功しました";
}
else
{
message = "失敗しました";
}
}
6-2. out引数には変数を渡す必要がある
out引数には、値を受け取るための変数を渡す必要があります。
次のように、数値や文字列を直接渡すことはできません。
C#GetNumber(out 10); // エラー
outは、メソッドが値を代入するための引数です。そのため、代入先となる変数が必要です。
C#GetNumber(out int number);
また、プロパティや一部の式はout引数として渡せない場合があります。基本的には、ローカル変数を渡すと覚えておくと安心です。
6-3. 読みやすさが下がる場合は使いすぎに注意
outは便利ですが、使いすぎるとコードが読みにくくなります。
C#GetUserData(out int id, out string name, out int age, out string email, out string address);
このようにout引数が多いと、どの値が何を意味するのかがわかりにくくなります。
複数の値を返したい場合でも、値の数が多いならクラスや構造体を使うことを検討しましょう。
C#class UserData
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public string Email { get; set; }
}
読みやすさや保守性を考えると、outは必要な場面に絞って使うのが重要です。
6-4. asyncメソッドではout引数を使えない
C#のasyncメソッドでは、out引数を使えません。
次のようなコードはコンパイルエラーになります。
C#async Task GetDataAsync(out int result)
{
result = 10;
await Task.Delay(1000);
}
asyncメソッドで複数の値を返したい場合は、タプルやクラスを使います。
C#async Task<(bool success, int result)> GetDataAsync()
{
await Task.Delay(1000);
return (true, 10);
}
呼び出し側は次のように書けます。
C#var result = await GetDataAsync();
if (result.success)
{
Console.WriteLine(result.result);
}
非同期処理では、outではなく戻り値としてまとめて返す方法を選びましょう。
6-5. outとreturnの役割を混同しない
outとreturnは、どちらも値を返すために使えますが、役割が異なります。
returnはメソッドの主な結果を返すために使います。
C#int Add(int a, int b)
{
return a + b;
}
outは、追加の結果や補助的な結果を返すために使います。
C#bool TryAdd(string a, string b, out int result)
{
if (int.TryParse(a, out int x) && int.TryParse(b, out int y))
{
result = x + y;
return true;
}
result = 0;
return false;
}
この例では、returnで処理の成功可否を返し、outで計算結果を返しています。
基本的には、メインの結果はreturn、追加の結果はoutと考えると整理しやすいです。
7. outを使わないほうがよいケース
7-1. 戻り値が多すぎる場合
outは複数の値を返せますが、戻り値が多すぎる場合には向いていません。
C#GetUserInfo(out int id, out string name, out int age, out string email, out string address);
このようなメソッドは、呼び出す側も読む側も理解しにくくなります。
値が多い場合は、クラスや構造体にまとめるほうが適しています。
C#class UserInfo
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public string Email { get; set; }
public string Address { get; set; }
}
複数の値に意味のまとまりがあるなら、outよりも専用の型を作るほうがよいでしょう。
7-2. 意味のあるデータ構造として返したい場合
返したい値が1つの意味を持つデータである場合、outよりもクラスや構造体を使うべきです。
たとえば、ユーザー情報を返す場合、outでバラバラに返すよりも、Userクラスとして返すほうが自然です。
C#class User
{
public int Id { get; set; }
public string Name { get; set; }
}
C#User GetUser()
{
return new User
{
Id = 1,
Name = "Taro"
};
}
このようにすると、データの意味が明確になり、あとから項目を追加する場合も管理しやすくなります。
7-3. タプルのほうが読みやすい場合
一時的に複数の値を返すだけなら、タプルのほうが読みやすい場合もあります。
C#(string name, int age) GetUser()
{
return ("Taro", 25);
}
呼び出し側は次のように書けます。
C#var user = GetUser();
Console.WriteLine(user.name);
Console.WriteLine(user.age);
outを使った場合と比べると、戻り値として結果がまとまっているため、処理の流れがわかりやすくなります。
ただし、TryParseのように成功可否をboolで返し、結果をoutで受け取るパターンでは、outのほうが慣習的でわかりやすい場合があります。
7-4. クラスや構造体を使ったほうが保守しやすい場合
長期的に使うデータや、複数のメソッドで共有するデータは、クラスや構造体にしたほうが保守しやすくなります。
C#struct CalculationResult
{
public int Total { get; set; }
public double Average { get; set; }
}
C#CalculationResult Calculate(int[] numbers)
{
int total = numbers.Sum();
double average = numbers.Average();
return new CalculationResult
{
Total = total,
Average = average
};
}
このように、計算結果を1つの型として表すことで、コードの意味が明確になります。
outは手軽ですが、設計としてわかりやすいとは限りません。保守性を重視する場合は、クラスや構造体も検討しましょう。
7-5. outと代替手段の使い分け
out、タプル、クラス、構造体は、それぞれ適した場面が異なります。
outは、処理の成功可否と結果を同時に返したい場合に向いています。
C#bool success = int.TryParse("123", out int number);
タプルは、少数の値を一時的に返したい場合に便利です。
C#(int min, int max) GetMinMax(int[] numbers)
{
return (numbers.Min(), numbers.Max());
}
クラスや構造体は、意味のあるデータのまとまりを返したい場合に向いています。
C#User GetUser()
{
return new User();
}
どれを使うか迷った場合は、次のように考えると判断しやすくなります。
処理の成功可否と結果を返したいならout、少数の一時的な結果ならタプル、意味のあるデータならクラスや構造体を使うとよいでしょう。
8. C#のoutに関するよくある質問
8-1. outとreturnは何が違う?
returnは、メソッドの戻り値として結果を返すために使います。
C#int GetNumber()
{
return 10;
}
outは、引数を通じて呼び出し元へ値を返すために使います。
C#void GetNumber(out int number)
{
number = 10;
}
大きな違いは、returnは通常1つの値を返すのに対し、outを使うと複数の値を返せる点です。
ただし、基本的にはメインの結果はreturnで返し、追加の値が必要な場合にoutを使うのが一般的です。
8-2. outとrefは同時に使える?
1つの引数に対して、outとrefを同時に付けることはできません。
C#void Method(out ref int value) // エラー
{
}
outとrefはどちらも引数の渡し方を指定するキーワードですが、役割が異なります。
outはメソッド内で値を代入して返すために使います。
refは、呼び出し前に入っている値をメソッド内で変更するために使います。
同じメソッド内で、別々の引数にoutとrefを使うことはできます。
C#void Method(ref int x, out int y)
{
x += 10;
y = x * 2;
}
ただし、読みやすさを考えると、outやrefが多いメソッドは避けたほうがよいでしょう。
8-3. out引数は初期化しなくても使える?
はい、out引数は呼び出し前に初期化しなくても使えます。
C#int number;
GetNumber(out number);
これは、out引数にはメソッド内で必ず値が代入されることが保証されているためです。
さらに、out変数宣言を使えば、呼び出し時に変数を宣言できます。
C#GetNumber(out int number);
ただし、メソッド側では必ず値を代入する必要があります。
C#void GetNumber(out int number)
{
number = 10;
}
8-4. out変数宣言はいつから使える?
out変数宣言は、C# 7.0から使えるようになった機能です。
以前のC#では、次のように事前に変数を宣言する必要がありました。
C#int number;
int.TryParse("123", out number);
C# 7.0以降では、次のようにメソッド呼び出しの中で変数を宣言できます。
C#int.TryParse("123", out int number);
この書き方により、変数のスコープを必要な範囲に限定しやすくなり、コードも読みやすくなります。
8-5. outは初心者でも覚えるべき?
はい、C#を学ぶ初心者でもoutは覚えておくべきキーワードです。
特に、int.TryParseやDictionary.TryGetValueは実務でもよく使われるため、outの基本を理解しておくとコードを読みやすくなります。
ただし、最初から複雑な使い方を覚える必要はありません。
まずは、次の形を理解しておけば十分です。
C#bool success = int.TryParse("123", out int number);
このコードは、「変換に成功したかどうかをsuccessで受け取り、変換結果をnumberで受け取る」という意味です。
このパターンを理解できれば、C#のoutの基本はかなり身についているといえます。
まとめ
C#のoutは、メソッドから呼び出し元へ値を返すためのキーワードです。
通常のreturnでは1つの値しか返せませんが、outを使うことで複数の値を返すことができます。
特に、int.TryParseやDictionary.TryGetValueのように、処理の成功可否をboolで返し、結果をoutで受け取るパターンは、C#で非常によく使われます。
outを使うときの重要なポイントは、メソッド内で必ず値を代入することです。呼び出し前の変数は初期化しなくても使えますが、メソッド側ではすべての処理経路でout引数に値を入れる必要があります。
また、outとrefは似ていますが、役割が異なります。outはメソッドから新しく値を受け取るとき、refはすでにある値をメソッド内で変更するときに使います。
複数の値を返したい場合でも、常にoutが最適とは限りません。少数の一時的な値ならタプル、意味のあるデータならクラスや構造体を使ったほうが読みやすく保守しやすい場合があります。
C#のoutは、初心者がつまずきやすい一方で、実務でもよく登場する重要な機能です。まずはTryParseやTryGetValueの使い方から慣れていくと、自然に理解できるようになります。

