初めに
とある関数に処理をさせて、その返り値を元にあれやこれやしよう!としたとき、その処理が正しく処理されたかは、戻り値からだとわかりませんよね。
例えば、割り算をする関数があったとします。
分母が0だと計算できずエラーになる為、例外として処理できますが、これだと呼び出し元の処理であれやこれやがしずらいですよね。
public int Divide(int numerator, int denominator)
{
if (denominator == 0)
{
throw new DivideByZeroException("0で割ることはできません");
}
return numerator / denominator;
}
それを解消方法として、ラップ型という方法があります。
今回は、そのラップ型についてのメモです。
ラップ型とは何か
ラップ型(Wrapper Type)は、あるデータや値を「包み込む」ことで、追加の情報や機能を付加した型のことを指します。
言い換えると、「元データ単体では扱いにくい部分を改善するために、それを包んで別の形で扱えるようにした型」 のことです。
例えば、「初めに」で記載した「その処理が正しくできていたか」は
以下のResultクラスの「成功か失敗かの状態」や「エラーメッセージ」などの付加情報を加えるとで分かります。
public class Result<T>
{
public bool IsSuccess { get; } // 成功かどうか
public T Value { get; } // 成功時の値
public string Error { get; } // 失敗時のエラーメッセージ
}
このように「元の値 + 追加情報」で返すことで、より扱いやすくする型がラップ型です。
よく使われる用途として、
・成功や失敗の状態を一緒に返したい。
・追加情報を格納したい(例:エラーについての詳細情報など)。
があります。
実際のコード
割り算を計算する処理を例に作成します。
・Result:ラッパー、Divideをラップする結果のクラス
・Divide:割り算を計算する処理
・Main:メインの処理
Resultクラス
public class Result<T>
{
public bool IsSuccess { get; } // 成功かどうかを示すプロパティ
public T Value { get; } // 成功時に返す値
public string Error { get; } // 失敗時のエラーメッセージ
private Result(T value, bool isSuccess, string error)
{
IsSuccess = isSuccess;
Value = value;
Error = error;
}
// 成功の時の静的メソッド
public static Result<T> Success(T value)
{
return new Result<T>(value, true, null);
}
// 失敗の時の静的メソッド
public static Result<T> Failure(string error)
{
return new Result<T>(default, false, error);
}
}
Resultクラスには、成功と失敗のメソッドを用意しインスタンス時にそれぞれの結果を格納しています。
成功時には結果を与え、失敗時には、エラーメッセージを付けることで原因が明確化します。
Divideメソッド
public Result<int> Divide(int numerator, int denominator)
{
if (denominator == 0)
{
return Result<int>.Failure("エラー: 0で割り算はできません");
}
int result = numerator / denominator;
return Result<int>.Success(result);
}
メソッドの返り値の型をResultにラッピングされたint型にします。
return Result<int>.Failure("エラー: 0で割り算はできません");
ResultのFailureメソッドでエラー理由を渡して、失敗判定にしています。
return Result<int>.Success(result);
ResultのSuccessメソッドで、計算結果を渡して、成功判定としています。
Mainメソッド
public void main ()
{
Var result = Divide(10, 2);
If (result.IsSuccess)
{
Console.WriteLine($"成功しました! 結果は: {result.Value}");
}
else
{
Console.WriteLine($"失敗しました。理由: {result.Error}");
}
}
代入されているresultはResult<T>クラスなので、
IsSuccessを使うことで成功・失敗の判定ができ、
成功時はValueを使うとDivideの結果を取得し、
失敗時はErrorでエラー理由を取得することが出来ます。
最後に
今回の例は、割り算処理にResultクラスをラップしましたが、他にもデータ通信やファイル操作など処理の成功・失敗を明確にしたい処理にラップすることでより扱いやすくすることが出来ます。
以上、ラップ型のメモでした。
##おまけ
Result<T>のTが何かというと、ジェネリクス(Generics) というC#の仕組みで使われる 「型引数(Type Parameter)」 の名前です。
ジェネリクスでは、具体的なデータ型(例: int, string, MyClass など)を明示せず、 「まだ決まっていない型」 を表すために、仮の記号(プレースホルダー)として名前を使います。
なぜTなのかと言うと、 "Type" (型)の略称で、「ここに型が入るよ」と示唆しています。
ただの名前なので、Resultとしても問題が無いらしいですが、今までの慣例的にResult<T>が主流らしいですね。