Effective C# 3rd 読書メモ 46 リソースの後始末にusingとtry/finallyを利用する

.NET Frameworkの管理下にない(アンマネージド)リソースを使用する型は、Dispose()メソッドによって明示的に解放すべきである。Dispose()メソッドをもつ型を使うときに、Dispose()を実行するのは あなたの 責任である。Dispose()が必ず呼ばれるようにする最良の方法は、using文を使うか、try/finallyブロックを使うかである。

public void ExecuteCommand(string connectionString,
    string command)
{
    using (var connection = new SqlConnection(connectionString))
    using (var sqlCommand = new SqlCommand(command, connection))
    {
        connection.Open();
        sqlCommand.ExecuteNonQuery();
    }
}

同様の処理をtry/finallyで書くと以下のようになる。

public void ExecuteCommand(string connectionString,
    string command)
{
    SqlConnection myConnection = null;
    SqlCommand myCommand = null;

    try
    {
        myConnection = new SqlConnection(connectionString);
        myCommand = new SqlCommand(command, myConnection);

        myConnection.Open();
        myCommand.ExecuteNonQuery();
    }
    finally
    {
        myConnection?.Dispose();
        myCommand?.Dispose();
    }
}

usingはコンパイル時の型がIDisposableを実装している場合にのみ利用可能で、任意の型に対して利用できるわけではない。

以下のように、usingをキャストと共に使うと、(1) objがIDisposableを実装している場合は、using文が実行される (2) objがIDisposableを実装していない場合は、using文は実行されない という結果になる。

object obj = Factory.CreateResource();
using (obj as IDisposable)
    WriteLine(obj.ToString());

Effective C# 3rd 読書メモ 45 メソッドの契約が満たされない場合に例外を使用する

メソッドが定められた振る舞いを行うことができない場合、例外によって失敗を報告すべきである。エラーコードの戻り値はたやすく無視されるし、エラーコードのチェックや伝播は正常系のコードを汚染し、中核となるロジックをわかりづらくする。

しかし、例外を通常の制御構造として使用してはいけない。このことは、publicメソッドを提供する際には、通常の使用で例外が投げられる確率をできるだけ減らさなければならない、ということでもある。

そのため、メソッドの事前条件が満たされるかテストするメソッドを別に提供し、例外を避けられるようにするのがよい(プログラマーが事前条件のテストを忘れれば例外が飛ぶが、これは正しい挙動である)。

internal static class Program
{
    public static void Main()
    {
        var worker = new DoesWorkThatMightFail();

        if (!worker.TryDoWork())
        {
            WriteLine("Fail");
        }

        WriteLine("Success");
    }
}

public class DoesWorkThatMightFail
{
    public bool TryDoWork()
    {
        if (!TestConditions())
            return false;
        Work();
        return true;
    }

    public void DoWork()
    {
        Work();
    }

    /// <summary>
    /// 事前条件のテスト
    /// </summary>
    /// <returns></returns>
    private bool TestConditions()
    {
        return true;
    }

    private void Work()
    {
    }
}

Effective C# 3rd 読書メモ 44 バインドされた変数を書き換えてはいけない

以下のコードで、sequence()デリゲートはindex変数をキャプチャしている。

public static void Main(string[] args)
{
    var index = 0;
    Func<IEnumerable<int>> sequence =
        () => Generate(30, () => index++);

    index = 20;
    foreach (int n in sequence())
        WriteLine(n);
    WriteLine("Done");

    index = 100;
    foreach (var n in sequence())
        WriteLine(n);
}

public static IEnumerable<int> Generate(int n, Func<int> func)
{
    for (var i = 0; i < n; i++)
        yield return func();
}

バインドされた変数を書き換えると、遅延実行との関係で予期せぬエラーを生むことがある。クロージャーにバインドされた変数の書き換えは避けるべきである。

Effective C# 3rd 読書メモ 43 Single()とFirst()によってクエリの意味をわかりやすくする

Single()はただ1つの要素だけを返す。要素が存在しなかったり、複数の要素が存在した場合には例外が投げられる。必ず1つ存在するものを取得するにはSingle()を使うのが良い。

0個または1個の要素が返る場合は、SingleOrDefault()を使うことができる。この場合にも、複数の要素があった場合には例外が投げられる。0個の場合にはnullが返る。

複数の要素の中で、1つだけ取り出したい場合には、First()またはFirstOrDefault()を使うことができる。

はじめのいくつかの要素を飛ばして取得したい場合、Skip()を使うことで飛ばすことができる。

var answer = (from p in Forwards
                  where p.GoalsScored > 0
                  orderby p.GoalsScored
                  select p).Skip(2).First();

Effective C# 3rd 読書メモ 42 IEnumerableとIQueryableのデータソースを区別する

IQueryableとIEnumerableはとても似たAPIシグネチャをもっている。また、IQueryableはIEnumerableを継承している。この2つのインターフェイスは原則として交換可能である。一方、一連の要素(シーケンス)は交換可能とは限らず、そのふるまいやパフォーマンスも大きく異なる。

以下のコードの最終的な出力はどちらも同じだが、裏側で実行される処理は異なる。

var q =
        from c in dbContext.Customers
        where c.City == "London"
        select c;
var finalAnswer = from c in q
                  orderby c.Name
                  select c;
// Code to iterate the final Answer sequence elided

var q =
    (from c in dbContext.Customers
     where c.City == "London"
     select c).AsEnumerable();
var finalAnswer = from c in q
                  orderby c.Name
                  select c;
// Code to iterate final Answer elided.

前者では、LINQ to SQLライブラリによって2つのLINQを合成してSQLが生成され、SQLが一度だけ実行される。後者では、はじめのLINQから生成されたSQLの実行後、その結果を使用して2番めのLINQがLINQ to Objectsによって実行される。多くの場合、IQueryableのまま使ったほうが、IEnumerableを使うよりもパフォーマンスが良い。

IEnumerableとIQueryableを透過的に扱いたい場合に便利なのがAsQueryable()メソッドである。

public static IEnumerable<Product>
    ValidProducts(this IEnumerable<Product> products) =>
    from p in products.AsQueryable()
    where p.ProductName.LastIndexOf('C') == 0
    select p;