Effective C# 第4章

Effective C# 6.0/7.0 | Bill Wagner, 鈴木 幸敏, 鈴木 幸敏 |本 | 通販 | Amazon

個人的な読書ノートであり、自分の解釈が誤っていたり要点がずれてる可能性があります。内容が気になる人は自分で読んでください。

難しくなってきたのはもちろん、翻訳怪しいところとか誤植がちらほら。 内容理解に苦しむ。

4章 LINQを扱う処理

  • ありとあらゆるデータソースに対して、同じ処理を同じ言語機能で実装できるようにするのが目標。
  • 独自のデータプロバイダを提供することも可能。

29. コレクションを返すメソッドではなくイテレータを返すメソッドとすること

  • 配列全体を返すのではなく IEnumerable<T> を返そう。
  • 効率的にコレクションを保持、生成できるようになる。
  • 必要な分だけを生成できる。
  • 最初の要素の要求時まで引数チェックが遅れる問題はメソッドを分離する。

30. ループよりもクエリ構文を使用すること

  • 読みやすくなることが多い。
  • シーケンスを処理するステップごとの中間データを保持する必要がない。 - Take, TakeWhile, Skip, SkipWhile, Min, Maxなどはメソッド呼び出し形式でしか使用できない。
  • クエリ構文が使えるなら使う、使えないならメソッド呼び出し形式を使う。
  • パフォーマンスのため再実装する前に、.AsParallell() による並列実行を試す。

個人的にはループによる構築のほうが読みやすく感じてしまう。いままでメソッド呼び出し形式しか使ったことがないが、SelectManyを含む場合はクエリ構文のほうが読みやすいな。

31. シーケンス用の組み合わせ可能なAPIを作成する

  • IEnumerable<T> を引数にうけとり、 IEnumerable<T> を返すような関数を作って再利用しよう。
  • yield return を使って書くと、継続可能な、遅延実行される関数になる。
  • 複数の処理を組み合わせた場合も、シーケンスの走査は1回しか行われないのでパフォーマンスがよくなる。
  • 入力シーケンスが参照型のときは書き換えられるけど書き換えたらダメ。

よく使うであろうシーケンス変換はこのようにしたい。

パフォーマンス良くなると言っても計算オーダーは変わらない。ランダムアクセス可能であることを利用したり、コレクション全体を保持することにより計算量を落とせるアルゴリズムも多いので、いつでも使えるわけじゃないだろうし、無理してつかうものでもないだろう。

32. 反復処理を Action, Predicate, Func と分離する

  • (1) シーケンスの反復の仕方を変える Func, Predicate
  • (2) 各要素に処理を実行する: Action
  • 機能を分離しておくことで、再利用性が高まる。

33. 要求に応じてシーケンスの要素を生成する

  • 要求されたタイミングで必要な分だけ要素が生成されるので、パフォーマンスがよくなる。

事前に一気に生成しておいたほうがパフォーマンスよい場合が多いので、同意できない。

34. 関数引数を使用して役割を分離する

  • コンポーネント間に必要な制約を記述するのに最適な言語機能を選ぶ。親クラス、インターフェース、関数引数など。
  • インターフェースを使用したり、親クラスを用意する方法は、利用者になかなか手間をかけることになる。
  • ジェネリックメソッドと関数引数で代用できることがある。
  • 例外処理など実行時エラー処理にかかる手間が増えるが、柔軟性が手に入る。

35. 拡張メソッドをオーバーロードしないこと

  • 拡張メソッドは型の機能を拡張する方法としては不適切。
  • usingステートメントの差し替えで動作を切り替えるのは間違ってる。
  • 拡張メソッドは、理論的に型の1機能とみなせるような機能に対してのみ使用すべき。

36. クエリ式とメソッド呼び出しの対応を把握する

  • コンパイラがクエリ式をメソッド呼び出しへと変換する。
  • コンパイル時点でシグネチャが一致するかどうかしか見てない。
  • where → Where
  • select → Select
  • orderby → OrderBy(Descending), ThenBy(Descending)
  • group by into → GroupBy (詳細?)
  • 複数の from → SelectMany
  • join → Join, GroupJoin

37. クエリを即時評価ではなく遅延評価すること

  • 遅延評価をうまく使うことで、かんたんにクエリを合成する事ができる。
  • 即時評価を使用すべき明確な理由がないのであれば、遅延評価を採用すべき。
  • スナップショットが必要なときは ToList, ToArray を使う。
  • Where, OrderBy, Max や Min はシーケンス全体を要求する。
  • クエリの早めにフィルタ関数を通すほうが良い。

38. メソッドよりもラムダ式を使用すること

  • クエリを共通化するのにIQueryable<T>をつかおう。staticメソッドなどを使って分離してはいけない。
  • クエリ構文のままラムダ式として保持しておけば、クエリ構文として再利用できる。
  • LINQ to SQL では IQueryable<T> クエリツリーが SQL へと変換される。

39. FuncやAction内では例外をスローしないこと

  • Actionの中で例外を使わない。
  • 例外が発生しうるActionを使う場合は、新しいシーケンスを返すクエリにする。成功を確認してから置き換える。

40. 即時実行と遅延実行を区別すること

  • 計算結果を引数にわたすか、計算関数をメソッドを渡すべきか。
  • 最大の判断基準は副作用。
  • 副作用を起こさず、即時評価も遅延評価も同じ結果になるなら、パフォーマンスで判断できる。

41. コストのかかるリソースを保持し続けないこと

  • クロージャやキャプチャされた変数によってObjectの生存期間が延長される。
  • ファイル読み込みをIEnumerable経由で行う → いつファイルを閉じれるか問題が発生
    • ファイルを開いてIEnumerableを返却するメソッドにする → 2回走査されると死
    • アルゴリズムを引数として渡すパターン → ちょっと複雑
  • ToList などにより即時評価させることで、すぐリソースを破棄できるようにするテクもある
  • C#コンパイラは1スコープにつき1ネストクラスを作成してクロージャを実装する。不本意にコピーされないか注意する。

42. IEnumerableとIQueryableを区別すること

  • IQueryableはIEnumerableを継承している
  • IQueryableは式ツリーとして処理され、データソースに近いところ(SQL分などとして)実行される
  • IEnumerableローカルマシン上で実行される。
  • データソースが IQueryable に対応しているなら積極的に使うべき。
  • AsQueryable() で IEnumerableもIQueryableに変換できる。

43. クエリに期待する意味をSingle()やFirst()を使用して表現すること

  • ちょうど1要素取るなら Single()
  • 0要素だったり2要素以上ある場合は例外になる
  • First(), FirstOrDefault(), Skip(2).Fist() などをつかう。
  • コードの意味を明確化できる

44. 束縛した変数を変更しないこと

  • C#はクエリおよびラムダ式を以下ののいずれかに変換する
    • 変数参照なし → staticデリゲート
    • メンバ変数の参照 → インスタンスデリゲート
    • ローカル変数やメソッドの引数 → クロージャ 
  • クロージャにキャプチャされた束縛変数は変更しないようにするべき