C#のvarとtry~catchは糞ではない

C#の機能に、try ~ catch 構文というものがあるのですが、極めて否定的な意見を検索上位に見かけたため、反論を試みたいと思います。

対象記事はこちらです。

C#のvarとtry〜catchが糞すぎる - やねうらお−ノーゲーム・ノーライフ

問題の確認

上記サイト中での問題となっているコードは以下の通り。

// 【サイトから引用】
try {
    var hoge = new HogeClass();
    hoge.XXX();
} catch {
    if (hoge!=null)
        hoge.YYY();
}

課題として、catch 節で hoge が参照できないようです。「スコープを抜けた後に変数を参照できない」事を問題としてしまうと、多くの言語で問題になるかと思いますが…

また、上記コードの改善案として以下を示しています。

// 【サイトから引用】
//var hoge = null;
HogeClass hoge = null;
try {
    hoge = new HogeClass();
    hoge.XXX();
} catch {
    if (hoge!=null)
        hoge.YYY();
}

そして最終的に以下を提案しています。(コンストラクタで例外は起きないんですね…)インスタンスの生成が失敗しているのにYYYを呼び出すことは無いようです。また、コンストラクタ内でアンマネージリソースを確保して例外で未開放のままになるケースではないようです。

// 【サイトから引用】
try {
  var hoge = new HogeClass();
  try {
    hoge.XXX();
  } catch {
    hoge.YYY();
    throw;
  }
} catch {
  //共通の例外処理
}

余談ですが、上記コードの carch のみの書き方は現在非推奨となっていて、catch(XXXEXception) のように型を明示して受ける書き方が一般的です。

また、インスタンス化した直後に XXX() を呼び出して例外が出たりでなかったりという副作用があるようですが、(この場合、staticな変数をメソッド内で参照しているか、外部リソースにアクセスしているかと)拡張性に問題が出るので必要なパラメータはいずれかの操作の引数もしくは、プロパティで渡す、外部リソースの利用は何らかの方法で明示しましょう。

var について

途中で HogeClass hoge = null を var hoge = null と宣言できないことでvarをタイトルの通り糞と断定しているようですが、C#におけるvarはJavaScriptのvarとは扱いが異なります。varはなんでも入れられる型(C#ではdynamicが近い)ではなく、式の右辺から型を推測して左辺を決定しているだけの具象型のシンタックスシュガーであり、varだからと言ってなんでも入るわけではありません。そして後から宣言時に決定した型を変更することも出来ません。

従って、静的型付けの言語の特性を無視しvarに不備があるとの事ですが、この場合ただの仕様誤認と言えそうです。

try ~ catchについて

try ~ catch の件はコード例を仕様化すると

  • HogeClassはインスタンス生成時に例外が発生する時がある
  • HogeClass#XXX(...)で例外が発生する可能性がある
  • インスタンス生成に成功してXXX(..)で例外が発生したらYYY(...)を実行したい

解決方法

上記ケースですが、HogeClassの所有権によって2通りの対応があります。

  • 自分の所有物であり変更可能な場合
  • 他人の所有物であり変更不可能な場合
自分の所有物であり変更可能な場合

この場、コードが変更できるので対処は簡単です。オリジナルのTryoutパターンを発明せずとも.NETによく見られる、TryParseパターンを適用します。[雑記] 例外の使い方 - C# によるプログラミング入門 | ++C++; // 未確認飛行 C

void XXX() を bool TryXXX()へ変更し、成功ならtrue、失敗ならfalseの戻り値を返すように以下の通りコードを変更します。

var hoge = new HogeClass();
if(!hoge.TryXXX())
{
    hoge.YYY(); // 実行に失敗している時だけここを実行する
}
他人の所有物であり変更不可能な場合

こういった場合、基本的に型をユーザーコード上で直接使用しないで1層アダプターパターン的なレイヤーを設けます。今回は作りが簡単なのでメソッドレベルでラップすることにします。

以下のように、インスタンス化に成功したオブジェクトを引数に渡せば問題の状況は発生しません。

public static void Main(params string[] args)
{
    InvokeHoge(new HogeClass());
}

public static void InvokeHoge(HogeClass hoge)
{
    try
    {
        hoge.XXX();
    }
    catch (HogeException ex)
    {
        hoge.YYY();
    }
}

もしくはメソッドにせずにラムダで処理しても良さそうです。

public static void Main(params string[] args)
{
    Action<HogeClass> hogeAction = hoge =>
    {
        try
        {
            hoge.XXX();
        }
        catch (HogeException)
        {
            hoge.YYY();
        }
    };

    hogeAction(new HogeClass());
}
まとめ

var も try ~ carch も利用性は低くありません。むしろ有用かつ、よく考えられていると思います。言語仕様を疑う前に仕様を把握し正しく利用するようにしましょう。