1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【C#】メソッドから異なる型の戻り値を2つ返すベストプラクティス

Posted at

【C#】メソッドから異なる型の戻り値を2つ返すベストプラクティス(テスト実装例付き)

🧩 背景

C#で「メソッドから異なる型の2つの値を返したい」という場面に遭遇したことはありませんか?

たとえば、以下のように「保存処理のレスポンス」と「実際にDBへ登録されたエンティティ」を同時に返したいケースです。

var response = await service.StoreAsync(request);
var actual = await dbContext.Foo.SingleOrDefaultAsync();

この2つをメソッドからまとめて返すにはどうすればよいでしょうか?
この記事では、タプル専用DTO/レコード型の2つの方法でのベストプラクティスを紹介します。


✅ 方法1. タプル (T1, T2) を使う(簡潔で便利)

タプルとは

最も手軽に「2つの戻り値」を扱うには、タプルが便利です。

private async Task<(ResponseDto Response, Foo? Actual)> ExecuteStoreAsync(...)
{
    var response = await service.StoreAsync(request);
    var actual = await dbContext.Foo.SingleOrDefaultAsync();
    return (response, actual);
}

呼び出し側の使用例

var (response, actual) = await ExecuteStoreAsync(...);
Assert.NotNull(response);
Assert.NotNull(actual);

👍 メリット

  • 実装がとてもシンプル
  • テストコード内での一時的な利用に最適

👎 デメリット

  • 意味が不明瞭になりやすい(Item1, Item2 に見えることも)
  • 戻り値が増えると混乱のもとになる

🔧 方法2. 専用のDTOやレコード型を定義する(拡張性&可読性◎)

タプルよりも少し構造が増えますが、拡張性や可読性を考えるとベストな方法です。

public record ExecuteStoreResult(ResponseDto Response, Foo? Actual);

private async Task<ExecuteStoreResult> ExecuteStoreAsync(...)
{
    var response = await service.StoreAsync(request);
    var actual = await dbContext.Foo.SingleOrDefaultAsync();
    return new ExecuteStoreResult(response, actual);
}

呼び出し側の使用例

var result = await ExecuteStoreAsync(...);
Assert.Equal("ExpectedName", result.Actual?.Name);
Assert.Equal(Status.Success, result.Response.Status);

👍 メリット

  • プロパティ名で意味が明確
  • 今後プロパティを追加しやすく、可読性も高い
  • 本番コードでも違和感なく使える

👎 デメリット

  • DTO/レコードを定義する手間は若干ある

🚀 どちらを選ぶべき?

条件 推奨方法
テストなど短期的・限定的に使う ✅ タプル
プロダクションコードや拡張を見越す ✅ DTO / レコード

❌ NGパターン:objectやdynamicで返す

以下のように objectdynamic を使うと型安全が失われ、非常に危険です。

object GetData() => return someCondition ? (object)"文字列" : 123;

これはテストでも本番コードでも非推奨です。


✅ まとめ

  • 戻り値を複数返したいとき、C#では タプル または DTO/レコード を使おう。
  • タプルは短期用途向け、DTO/レコードは長期・本番用途向け
  • objectdynamic は極力使わない方がよい。

🙋‍♂️補足

この記事では以下のような実装シーンを元にしています:

var response = await service.StoreAsync(request);
var actualMBD = await GetMaintenanceBuildingDataAsync(dbContext, request.ConstructionNumber);

テストコードでこのように2つの型を扱いたくなったら、ぜひこの記事の手法を活用してみてください!


📌 関連タグ

#CSharp #ユニットテスト #設計 #DTO #Tips


👍 この記事が役に立ったら、いいね・ストックいただけると励みになります!

1
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?