書評:「究極のC#プログラミング 新スタイルによる実践的コーディング」のまとめ

「究極のC#プログラミング 新スタイルによる実践的コーディング」を読んだので、まとめてみました。 自分用のメモなので、適当にまとめちゃってますが。

3.新しい繰り返しスタイル

yield return

  • yield return 文yield break 文反復子ブロックと呼ばれている。
class Range
{
    private int from, to;

    public IEnumerator<int> GetEnumerator()
    {
        for (int i = from; i <= to; i++) yield return i;
    }

    public Range(int from, int to)
    {
        this.from = from;
        this.to = to;
    }
}
class Program
{
    static void Main(string[] args)
    {
        foreach (var i in new Range(0, 9))
        {
            Console.Write("{0}", i);
        }
    }
}
  • 反復子ブロックで意識すること
    • 反復子ブロックはすぐには実行されない
    • 実行されるのは、foreach文で列挙が開始された後
    • 反復子ブロック内でyield return 文が実行されると、その引数の値がforeach文に渡り、1回実行される
    • foreach文に指定された実行文(上だとConsole.Write)の実行が終わると、反復子ブロック内の続きが実行される。

5.null許容型

null許容型とは

  • 値型でもnullを保持できるようにする。
  • 値型の型名の後ろに?を付与するとnull許容型になる
private int? num

null許容型ができた背景

  • 参照型と値型が同じように扱えるため。
  • ただ、すべての状況において、値型にnullを許容してしまうと、実行速度低下や必要メモリ増加の悪影響が出る。
  • そのため、nullを許容したい場合だけに限定されることが可能。
  • 逆の言い方をすると、nullになることが絶対にありえない変数の型には使うべきではない。

6.ラムダ式(全編)

ラムダ式は上位スコープにアクセス可能

 static void Main(String[] args)
    {
        string message = "hello world";
        
        var action = new Action(() => { Console.WriteLine(message); });
        action();
    }
  • 必要とされるあらゆる情報を引数経由で渡さずに済むので、引数の肥大化を防ぐ
 static Action CreateAction()
    {
        string message = "Hello World";
        return () => { Console.WriteLine(message); };
    }

    static void Main(String[] args)
    {
        var action = CreateAction();
        action();
    }
  • こういうのもOK。
  • ラムダ式が上位スコープの変数を参照しているとき、その変数の寿命をラムダ式の寿命が尽きるまで延命される。

ラムダ式で継承を置き換える

ラムダ式に置き換えた利点としては、ソースコードに書き込む文字数が減り、楽ができ、間違いが混入する可能性も減る。

実際のラムダ式で置き換えたコードは、以下の通り。


class Person
    {
        public Person(Action sayMyName)
        {
            SayMyName = sayMyName;
        }


        public Action SayMyName { get; }


        public static Person createStringNamePerson(string name)
        {
            return new Person(
                () => Console.WriteLine(name)
            );
        }

        public static Person createCharNamePerson(char name)
        {
            return new Person(
                () => Console.WriteLine(name)
            );
        }
    }

    class Program
    {
        static void Main(string[] args)
        {

            Person[] persons =
            {
                Person.createCharNamePerson('L'),
                Person.createStringNamePerson("サンプル 太郎"),
            };

            Array.ForEach(persons, person => person.SayMyName() );
        }
    }

8. 部分クラスと静的クラス

部分クラス

部分クラスとは

  • クラス・構造体・インターフェースの宣言を複数のファイル等に分割できる

メリット

  • フレームワークによって自動生成されるコードとプログラマーが記述するコードをファイル分割できる。
  • 自動生成されるコードは、プログラマーが直接修正すると、危険なので、容易に書き換えられないようにすることで安全性が高まる。
  • 既存のソースコードを書き換えることなく、機能強化を実施できるため。

静的クラス

  • デザインパターンでいうところのシングルトンパターンと同じ。
  • クラス宣言の際に、staticキーワードを付けるだけ。
  • いわゆるユーティリティクラスとかで使われている
  • C#3.0で上記の専用の構文が追加された経緯として、特殊なクラスの使い方ではなく、頻出する典型的な使い方に変化。

11. フレンドアセンブリ

12.varによる変数宣言とコレクション初期化子

varキーワード

  • 「暗黙的に型指定されるローカル変数」を宣言するためのキーワード
  • varキーワードを付与する場合は、必ず変数宣言時に初期化する必要あり。
  • varキーワードの型は、常に同じなので、途中で入れ替えることは不可(宣言時がintで、stringを代入できない)

varキーワードを使う意図

  • クラス名が極端に長すぎてソースコードの読みやすさを損なっている場合
  • 同じ型名が2回書かれている場合
  • newを使わない初期化で使うべきかは微妙なところ。

verキーワードが使用できない場面

verキーワードを活用できる場面

13.自動実装と自動定義

自動実装プロパティ

通常のプロパティ定義

 class SomeClass1
    {
        private int a;

        public int A
        {
            get { return a; }
            set { a = value; }
        }
    }

自動実装されるプロパティ定義

 class SomeClass2
    {
        public int A { get; set; }
    }
  • 自動実装プロパティは、常にgetアクセサとsetアクセサの双方が必要。

オブジェクト初期化子

オブジェクトのフィールド・プロパティを初期化する手段

  • フィールドの宣言時に初期値を指定
  • コンストラクタで書き込む
  • オブジェクト初期化子を使う

オブジェクト初期化子の本質

  • private /readonlyなフィールドでは初期化できない
  • オブジェクト初期化子が、インスタンス作成中に実行されず、できてから実行されるため。
  • ただ、コレクションはreadonlyでも初期化子は利用可能。

14.拡張メソッド

概要

  • 既に存在するクラスに対して、そのクラスを変更することなく、メソッドを追加できる機能。
  • クラスを変更することなくということは、コンパイル済みのバイナリーを変更することなく追加できること。を意味する。
  • シールクラス(sealed)を拡張することも可能。

注意点

  • アクセス制御
名前 public protected private
部分クラス
継承 ×
拡張メソッド × ×
  • 既存メソッドの振る舞いを拡張メソッドで定義しても、差し替えることは不可能。
  • 拡張メソッドはオブジェクト外の存在である、オブジェクト内部に手を出すことができない。

拡張メソッドを使用すべきとき

  • 可能であれば拡張メソッドは使わないほうがいい。
  • 既存のクラスライブラリに対して、「このメソッドがあれば便利」というような状況の場合に使用する。
  • 例えばDatetime 型に対して、月末日を取得するメソッドを用意する場合など。

15.LINQとクエリ式

LINQのメリット

  • 簡潔で読みやすい(特に複数の条件をフィルと処理する場合)
  • 強力なフィルタ処理、並び替え、グループ化機能を最小限のアプリケーションコードで実現
  • ほとんど変更せずに、他のデータソースに移植できる
  • Parallel LINQを用いると、クエリを並行して実行可能

LINQを使ううえでの注意点

  • クエリ対象のコレクションの中身が変化しても、クエリオブジェクトを作り直す必要はない。
  • 逆に、ある瞬間のクエリ結果を保存しておきたければ、クエリオブジェクトを即座に列挙しておく必要あり。
  • どれほど膨大なデータがヒットするクエリであろうと、クエリオブジェクトを作成するだけなら、ほとんど時間はかからないし、メモリも消費しない。
  • どれほど膨大なデータがヒットするクエリであろうと、単にそれらを列挙するだけならほとんどメモリは消費しない。