きなこもち.net

.NET Framework × UiPath,Orchestrator × Azure × AWS × Angularなどの忘備録

Effective C# 6.0/7.0 × 1章:C#言語イディオム&2章:リソース管理 × 自分なりの要点まとめ

目的

Effective C# 6.0/7.0を読んだ。 流し読みにならないように、1項目毎に要約してみた。
まとめてみた中で、心に残ったところを抜粋。 今回は1章:C#言語イディオム2章:リソース管理の項目を対象とする。

項目3:キャストにはisまたはasを使用すること

  • 安全にキャストをするための方法。1)as 演算子を利用する。2)is演算子で変換可能性を確認した後、asまたは、キャストを利用して変換を行う。
  • as はNull許容型もしくは参照型のインスタンスにしか利用できない。また、指定された型から派生した型への変換できない。変換に失敗したときは、nullを返す。
  • キャストは、指定の型への変換演算子を利用できる。参考)Implicit演算子
  • サンプルコードで出てきたImplicit演算子についてはこちら

項目5:カルチャ固有の文字列よりもFormattableStringを使用すること

  • 補完文字列は、暗黙的な型変換では、String型の文字列インスタンスとして生成されるが、FormattableStringを明示的に指定することも可能。
Func<FormattableString, string> ToGerman = (source) =>
 {
     return string.Format(System.Globalization.CultureInfo.CreateSpecificCulture("de-de"),
         source.Format,
         source.GetArguments());
 };
var test = $"Test{DateTime.Now}";
FormattableString test2 = $"Test{DateTime.Now}";
//Console.WriteLine(ToGerman(test)); //コンパイルエラー
Console.WriteLine(test2);
Console.WriteLine(ToGerman(test2));
Console.WriteLine(ToGerman($"Test{DateTime.Now}"));

項目7:デリゲートを使用してコールバックを表現する

  • シンプルな例
var numbers = Enumerable.Range(1, 200).ToList();
var oddNumbers = numbers.Find(n => n % 2 == 1);
  • 有用な場面1:イベントと組み合わせて使用。
  • 有用な場面2:特定のクラス間でデータをやり取りする必要があるが、互いのインターフェースを利用するほどの連携をさせたくない場合。

項目8:イベントの呼び出し時にNull条件演算子を使用すること

public class EventSource
{
    private int counter;
    private EventHandler<int> Updated;
    Public void RaiseUpdates()
    {
        counter++;
        if (updated != null)
            Updated(this,counter);
    }
}
  • Null条件演算子を利用することで、↑の従来の書き方を↓のようにすることができる。コンパイラがすべてのデリゲートもしくは、イベントの定義に対してタイプセーフなInvokeメソッドを生成する。上下で同じ挙動をするため、下のコードもスレッドセーフで実行できる。(Updatedを実行しようとしたタイミングで、別スレッドの処理でデリゲートがイベントハンドラから削除された場合でも、元のスレッドではデリゲートへの参照をなくさずに実行することができる。)
public class EventSource
{
    private int counter;
    private EventHandler<int> Updated;
    Public void RaiseUpdates()
    {
        counter++;
        Updated?.Invoke(this,counter);
    }
}   

項目12:メンバには割り当て演算子よりもオブジェクト初期化子を使用すること

  • オブジェクト初期化子は、コンストラクタの初期化処理より前に実行される。オブジェクト初期化子でも初期化し、コンストラクタでも初期化すると、無駄にインスタンスを作成することとなる。どちらかに統一するため、初期化子を利用するようにしたほうが良い。
  • 例外もある。オブジェクトのメンバ変数の中に、初期化のタイミングで例外を発生させる可能性のある者がいれば、コンストラクタ内で初期化するようにしたほうが良い。例外処理ができるため。
  • そのほかに、コンストラクタの引数に応じて初期化条件が変わる場合もコンストラクタで初期化したほうが良い。
  • オブジェクト初期化子にまとめることで、後々追加されるコンストラクタで、初期化処理を忘れるメンバー変数が出てくることを防ぐこともできる。

項目13:Staticメンバを最適に初期化すること

  • 項目12の初期化のタイミングで例外を発生させる可能性がある場合と同じ理由。Staticメンバが初期化に失敗すると、アプリケーションが終了していまう。 - Staticコンストラクタの例(シングルトンパターン)
public class SampleItem
{
    //単純な領域確保だけであれば、初期菓子を使う。
    private static readonly SampleItem theoneAndOnly = new SampleItem();
    public static SampleItem MyProperty 
    {
        get { return SampleItem.theoneAndOnly; }
    }
    private SampleItem() { }
}
  • staticの場合も、非Staticのと同じで、メンバ初期化子が実行された後に、コンストラクタが実行される。どちらも同時に初期化する必要はない。
public class SampleItem
{
    //複雑な初期化処理が必要な場合には、初期化子より処理は後に実行されるが、Staticコンストラクタを利用するようにする。
    public string Name { get; set; }
    private static readonly SampleItem theoneAndOnly;
    public static SampleItem MyProperty
    {
        get { return SampleItem.theoneAndOnly; }
    }
    static SampleItem()
    {
        try
        {
            theoneAndOnly = new SampleItem();
        }
        catch (Exception)
        {
            throw;
        }
    }
    private SampleItem() { }
}

項目16:コンストラクタ内では仮想メソッドを呼ばない

  • あるBaseクラスを継承した派生クラスがある場合、派生クラスのフィールド変数の初期化→Baseクラスのコンストラクタの実行→派生クラスのコンストラクタの実行の流れで処理が進むことになる。
  • この時、Baseクラスのコンストラクタ内でBaseクラスで定義されたVirtualメソッドを呼び出す処理がある場合、派生クラスで実装されたVirtualメソッドが、派生クラスのコンストラクタ実行前に実行されてしまうケースがある。
  • 期待していない順序での処理の実行を防ぐためにも、コンストラクタ内でのVirtualメソッドの呼び出しは避けるべきである。