関数を無視しないでください


戻り値を無視することは、しばしば微妙な方法で危険でありえます、しかし、あなたはもうあなたさえ気がつかないようにするためにそれに慣れているかもしれません.いくつかの時点で、関数の戻り値を忘れることによって引き起こされた問題に遭遇する可能性があります.それはあまりにも多くの可能性があまりにも多くの機能の戻り値を捨てることはあまり考えていない.ほとんどの場合、そうすることの本当の影響はありません.しかし、もしあなた自身が頻繁にこれを行うことを見つけるなら、それはなぜ質問をすることが重要です.おそらく、関数はこの値を理由で返していますか?APIのデザインに何か悪いことがありますか?

日付を加えることは、それを変えません
このようなコードを見たことがありますか?
var date = new DateTime(2000, 01, 01);

date.AddYears(1);
Console.WriteLine($"{date.ToShortDateString()}");
これはかなり簡単ですが、このコードにバグがあります.あなたがdotnetに精通しているならばDateTime APIは、明らかなものかもしれないが、それはあなたが前にそれを使用したことがない場合は、微妙で混乱することができます.
問題は、あなたが呼び出すときですAddYears これは日付を変更しない代わりに、新しい日付を返します.したがってWriteLine が呼び出されます.2000/01/01 の代わりに2001/01/01 あなたが予想したかもしれないように.
これを正しく動作させるには、新しい変数をdate変数に代入することで新しい日付を取得する必要があります.
var date = new DateTime(2000, 01, 01);

date = date.AddYears(1);
Console.WriteLine($"{date.ToShortDateString()}");
ではなぜAddYears 既存の日付に年を加える代わりに、新しい日付オブジェクトを返しますか?日付が不変の型であるため、これを行う.これは、一度変更したり、変更することはできません任意の方法で日付を作成したことを意味します.場合は、常に新しいものを作成する必要があります別の日付が必要です.
それが考慮しなければならない可能性を制限することによって複雑さを管理するのを助けることができるので、不変性自体は非常に役に立つ技術です日付を変更する方法は一つだけです.しかし、あなたがそれらを探していないならば、上記の例のような問題は見つけにくいです.もしC Chorzコンパイラがこのような問題を検出することができれば、それは素晴らしいことではないだろうし、最初の場所でミスを犯すのを防ぐ!

asyncと待つのを忘れた
別の問題をちょっと見てみましょう.2つのasync関数を呼び出すが、それらのうちの1つを待つのを忘れるasync関数を持っていたと言います.
async Task<ActionResult<Resource>> GetResource(string id) {

    AuditRequestAsync(id); // Forgot to await this, oops

    return await LoadResourceAsync(id);
}
この場合、不足しているかもしれないということをあなたに話しているC Count -コンパイラから警告を得ます.これは非常に時間の大半は、これはほぼ間違いなくバグです.しかし、あなたの警告を監視する方法に密接に依存して、それはまだデフォルトのコンパイラオプションを使用してアプリケーションをコンパイルすることが可能です.しかし、私たちが同じ機能を持っているが、asyncキーワードなしで何が起こるか、そのように.
Task<ActionResult<Resource>> GetResource(string id) {

    AuditRequestAsync(id);

    return LoadResourceAsync(id);
}
意味的に、この機能は全く同じです、それも同じバグから苦しみます、しかし、今度は、警告さえありません.この種の問題は、asyncキーワードを必要とせずにタスクを返すだけで機能が始まることができるので、より一般的に見えます.上記の例ではAuditRequestAsync 関数は後で、または別の著者によって追加されました、彼らはAsyncのキーワードを追加することを忘れても、プログラムが幸せにコンパイルされます.
最悪の場合は、関数はいくつかのケースで働くかもしれません.The AuditRequestAsync 関数はまだ実行されるが、await 終了時に呼び出し元がまだ保証されることはありません.場合によってはエラーになるかもしれませんmultiple active result sets 両方がデータベース呼び出しをするならば.他の人では、何かが全く間違っているのに気がつかないかもしれません.これらのような問題はしばしば他の変化が彼らを引き起こすまで休止状態にあることができます、あるいは、決定的でない(または、少なくとも明白でない)行動の結果は彼らを追跡して、修正するのが非常に難しいです.

暗黙のうちに関数を無視することは危険です
これらの例が共通しているのは、関数が返す値ですAddYears and AuditResourceRequestAsync ) は暗黙的に無視され、結果としてバグが発生した.コンパイラが警告またはエラーが値を使用しなかったか、暗黙のうちに無視したことを示すエラーを出したなら、これらの問題は以前に捕えられたかもしれないか、完全に妨げられるかもしれませんでした.
この問題に苦しむ多くのシナリオもあります.たとえば、APIのようなLINQ、リアクティブな拡張子、構造体、およびすべての不変の型を使用する場合、値を使用することを忘れる場合はほぼ間違いなくバグです.通常の関数であっても、特に副作用のない値を返す値は、戻り値が無視されたり忘れられたりしたことを明らかにするのに有益です.

明示的に無視する関数は安全です
値を使用したくない場合、暗黙のうちに未使用値を捨てる代わりに、明示的に破棄する必要があります.
未使用の値をキャッチするには、すべての未使用の値の警告を作成するには、カスタムアナライザを使用することができます不足だけではなく、非同期関数の中で待機します.しかし、このようなケースのいくつかについては、いくつかの類似した既存の分析器がありますasync await .
未使用の戻り値に対する警告があると、未使用の値がバグか何か明示的に破棄されることがあります.
例えば、タスクは、可能な不足している待ち時間に関する警告をもたらします.タスクを無視したい場合はstandalone discard 他の人にあなたがそれを気にしないということを知っているようにしてください.
_ = Task.Run(() => { ... }) // Explicitly discard the result
これは、値を使用しない意識的な決定をしたことを明確にします.他の開発者は、それを使用することを忘れることとは対照的に値を使用しないように決定されたことを示しています.
誰かがそのコードを読むとき、それは違いです:

  • 彼らは戻り値を使用することを忘れましたか?私は調査する必要がある.

  • 返り値を無視することを選んだ.私は移動できます.
  • 物事をより明示的にバグを防止し、あなたと他の多くの時間を節約できます.

    これは本当に働くでしょうか
    そこには多くのコードがある今日、それは心に明示的な破棄なしに書かれた.そして、私はC一世の世界が一晩で劇的に変わることを期待しません.それにもかかわらず、この記事の目的は、あなたのエディタが未使用の変数について警告しているのと同じ方法で、未使用の戻り値について開発者に警告しているアナライザに興味を持っているようにすることです.
    あなたはまだC Cのどれかのコードは、明示的に無視することなく書かれている場合は、これも実用的ではないだろうか?最近、関数の戻り値が使われていない場合は、コンパイラの警告を発しています.だから、今日のようにドネットがあっても、とても驚きました.私は、私が思ったように、半分の価値を捨てる必要はありませんでした.
    大多数のコードは単一の破棄を必要としなかった.私は破棄を使用する必要がいくつかのケースがありました、例えば、可能な流暢なAPIの終わりに値を捨ててください.
    その場合、式を明示的に“キャップオフ”するために拡張メソッドを使用します.
    builder
        .RegisterType<HttpClient>().AsSelf()
        .InstancePerLifetimeScope()
        .Ignore() // <- Takes a type <T> and returns a void
    
    私は、しかし、私が明らかに私が持ってはならない値を捨てた少なくとも一つのバグに、まだ走りました.最終的には、明示的に破棄された値でさえ、控えめに行われるべき何かであることがわかりました.戻り値を破棄するたびに、代わりにコードを再考していることがわかった.
    ステータスコードやリターンコードを使用したりログを記録したりする傾向がありました.
    var response = await httpClient.PostAsync(url, null);
    log.Information("Responded with {StatusCode}", response.StatusCode);
    
    私はバックグラウンドでタスクを実行したい場合は、タスクサービスでタスクを保持し、それがすべてのバックグラウンドタスクのエラー処理を簡素化.
    BackgroundService.Register(async () => {...})
    
    // Instead of
    
    _ = Task.Run(async () => {...}) // Hope you handled the exceptions in here
    
    ビルダーパターンまたは流暢APIを使用した場合は、変更不能なAPIを使用して、変更可能なAPIを使用する代わりに値を返すことを考えました.たとえば、LINQ VSをリストAPIを使用します.
    public IEnumerable<Out> GetResults(IEnumerable<In> items) => items
        .Where(x => ...)
        **.Select(x => ...)
        .OrderBy(x => ...)
    
    私はまた、値を返さない関数についても慎重です.

    私は興味があるかもしれない
    私が最初に新しい概念やテクニックをまたいでつまずくたびに、私はそれがどのように動作するための良い感じを得るためにしばらくの間それを再生する必要があることがわかります.アナライザを構築し、どこに警告が表示されます.上の警告をゼロからプログラムを書いてみてください.あなたがショートカットをとって、アナライザがあなたを連れて行くところに続くよう誘惑されないように、エラーとして警告をオンにしてください.
    しかし、最も重要なことは、自分でそれらを使用せずに値を捨てることを見つける場合は、なぜ自分自身を尋ねる.