はじめに
C#歴半年ですが、最近refを知りました。
Javaの経験はあり、Javaにもあるようなことを使ってそれなりに動くものを作成していました。
JavaにはないC#特有の機能について、ネットで説明やサンプルコードを見たことはあるのですが、使いどころが分からず使ったことがないものもいくつかあります。refは人から聞いて初めて知りました。
こう使うのか!と思って例として出したかったのですが、コメントでご指摘をいただきまして使いどころではなかったことが分かりました。
結果として、私の試行錯誤と学習メモのような形になってしまい参考にならないかもしれませんが、一つの例としてまとめます。
適切ではなかった事例
Before
C#歴1ヶ月そこらで作成した拙い共通チェックサービスです。
CheckModelにチェックしたい値を入れてチェックするようにしています。
class PasswordCheckService
{
public bool Execute(CheckModel model)
{
// 更新の場合
if (model.passward != null)
{
if (現在のパスワードやパスワード履歴と一致する)
{
return false;
}
return true;
}
// 一意チェック
if (DB検索結果が1件以上)
{
return false;
}
return true;
}
}
各画面のコントローラ(一部抜粋)
// パスワード妥当性チェックを行う
PasswordCheckService checkService = new PasswordCheckService();
bool CheckResult = checkService.Execute(model);
// チェック結果NGだったら
if (!CheckResult)
{
// エラーメッセージを設定
viewModel.ErrorMessage = "入力したパスワードは使用できません。";
}
やりたいこと
if文ごとに違うエラーメッセージを設定したい
考えたこと
-
案1
サービス:戻り値をViewModelにし、エラーがあったらViewModel.ErrorMessageにエラーメッセージを設定する。
コントローラ:ViewModel.ErrorMessageの値があったら画面を再表示し、エラーメッセージを表示させる。 -
案2
サービス:チェックごとにメソッドを分ける。
コントローラ:各チェックメソッドを使用して、それぞれエラーメッセージを設定し、画面を再表示させる。
⇒共通チェックサービスを使用するメンバーと話し合った結果、今回は別案のrefで対応することとなりました。
After
共通チェックサービス
class PasswordCheckService
{
// 引数に「ref」を追加
public bool Execute(ref CheckModel model)
{
// 更新の場合
if (model.passward != null)
{
if (現在のパスワードやパスワード履歴と一致する)
{
model.ErrorMessage = "入力したパスワードは使用できません。";
return false;
}
return true;
}
// 一意チェック
if (DB検索結果が1件以上)
{
model.ErrorMessage = "パスワードとXXXXが一意ではありません。";
return false;
}
return true;
}
}
各画面のコントローラ(一部抜粋)
// パスワードの妥当性チェックを行う
PasswordCheckService checkService = new PasswordCheckService();
// 使う側でも「ref」の記載が必要
bool CheckResult = checkService.Execute(ref model);
// チェック結果NGだったら
if (!CheckResult)
{
// エラーメッセージを設定
viewModel.ErrorMessage = model.ErrorMessage;
}
※後で気付いたのですが、一意でないことをメッセージとして出すのはよろしくないかもしれないと思いました。元々のエラーメッセージでよければ今回のことは全て意味のないことになってしまいますが…
いただいたご意見について
引数のCheckModelが参照型なので「ref」を使わずとも同じ動作をするとのことでした。
恥ずかしながら私はそのことを知らず、結果は戻り値にしか反映できないものだと思っていました。
確かにrefを消しても変わらない結果となりました。
しかし、refも何も書いていないと、戻り値以外にも値が変わっていることに気づきにくいのではないかと思いました。
可読性もあまりよろしくなく、バグを生む可能性があるのではないかと思います。
そのため、上記案2に書いたように、チェックごとにメソッドを分けて、使用する側でメッセージを設定するのがよいのではないかと考えました。
実際に案2を書いてみた
共通チェックサービス
class PasswordCheckService
{
// 履歴チェック
public bool HistoryCheck(CheckModel model)
{
if (現在のパスワードやパスワード履歴と一致する)
{
return false;
}
return true;
}
// 一意チェック
public bool UniqueCheck(CheckModel model)
{
// 一意チェック
if (DB検索結果が1件以上)
{
return false;
}
return true;
}
}
各画面のコントローラ(一部抜粋)
// パスワードの妥当性チェックを行う
PasswordCheckService checkService = new PasswordCheckService();
// 履歴チェック
if (!checkService.HistoryCheck(model))
{
// エラーメッセージを設定
viewModel.ErrorMessage = "入力したパスワードは使用できません。";
}
// 一意チェック
if (!checkService.UniqueCheck(model))
{
// エラーメッセージを設定
viewModel.ErrorMessage = "パスワードとXXXXが一意ではありません。";
}
今回をきっかけに、参照渡しや値渡し、ref/out/inの違いなどもネットで調べました。
これらに関しては多くの方が書いており、分かりやすそうな記事を挙げておきます。
参照渡し - C# によるプログラミング入門
C# ref outの違いとそれぞれの使い所
Javaの参照渡しと値渡しについてこの世で1番分かり易く解説してみた
最後に
C#の経験、開発経験が浅いメンバーで開発しているのでご意見大変ありがたいです。
今後も随時内容を更新していけたらと思います。