GoとSwiftでよく知られるものなのであるが、C#でも 提案#1398にてDeferステートメントの希望がされている。概念について把握していない方のために解説をすると、これはfinallyブロックがコードの最後でなく最初に現れるものと説明できる。
これがその提案の例。
{
SomeType thing = Whatever...;
defer {
thing.Free();
}
// thingを使用したコード
}
これは次のように書き換えられる。
{
SomeType thing = Whatever...;
try
{
// thingを使用したコード
}
finally
{
thing.Free();
}
}
これはfinallyブロックとほぼ似ており、多くの開発者が冗漫な文法であると考えている。MicrosoftのNeal Gafter氏は次のような利点があると反論した。
- IDisposableのようなインターフェースの実装すること、もしくは行うオブジェクトを生成するヘルパーを必要としません。
- 1つより多くあったときに扱いづらくなるような、コードのインデントのレベル上げが不要となります。
- try-finallyのようにクリーンアップコードを最後に移動させることなく、むしろクリーンアップのための宣言と初期化が一緒になることによって、論理的にクリーンアップコードを維持することができます。これによりコードをより簡単に理解できるようになります。
これをAlireza Habibi氏は次のように退けた。
私は本当の問題は、usingステートメントが追加のインデントが必要となる、ということでも、実装のインターフェースが必要となる、ということでもないと思います。それは全く悪いということはなく、クリーンアップが必要な型への約束となりますし。ここでの本当の問題は、disposableの型をdisposeし忘れる可能性があるということだと思います。Deferステートメントはそれの助けにはならず、これはむしろ、IDisposableインターフェースの実装をしないことや、IDisposableインターフェースがありそうな雰囲気にもかかわらずコード内で明確にCloseやFreeメソッドを呼び出してしまうことを助長します。
Sam氏はまた、この新しいキーワードについて質問をしている。
もしdeferの使用用途がリソースのクリーンアップであるとき、それは私にとってIDisposableの実装を必要としないことはアンチパターンであると見えてしまいます。IDisposableの実装で、ローカル変数として宣言してdisposeしなかったときに、ツールが警告できるようになります。もし、リソースに対してIDisposableが必要でないということを許してしまうと、この利点が失われてしまいます。
他の不満は処理の順序の理解が難しくなるということだ。HaloFourというハンドルネームのユーザーは、次の例を示した。
static void Main() {
Console.WriteLine("Hello"); //1
defer {
Console.WriteLine("Foo"); //5
}
defer {
Console.WriteLine("Bar"); //4
}
defer {
Console.WriteLine("Baz"); //3
}
Console.WriteLine("World"); //2
}
わかりやすくするために、各行が実行される順序をコメントとして追加した。見てわかる通り、コードは(行がいくつかスキップされながら)上部から下部に実行され、そして下部から上部に実行される。
例外のハンドリング
ある1つ質問として、deferブロックでの例外の効果がどのようになるかというものが話し合われた。通常のtry-finallyでは、現在のfinallyブロックは中止されるが、それを囲んでいる他のfinallyブロックは実行される。deferのケースでもそのようになるのか、または、最初の失敗が他のDeferにも伝達して中止されるのか?deferブロックへの全体的な合意への話し合いは残っており、引き続き行われる。
Swiftはこの問題を避けるために、deferブロックでは例外の発生させる可能性があるコードの呼び出しを認めていない。
Deferステートメントの提案は現在はC# 8.x candidateとしてタグ付けされているが、これは実際に将来のC#のリリースに含まれるということは意味していない。その公式なステータスは「Proposal champion」であり、これは将来のLanguage Design Meeting(LDM)での申し立てとして、C#のチームメンバーが興味を持っているということを意味する。