golang
にはdefer
という機能があります。
defer
を使うと関数を抜けるときに処理を実行することができます。
以下にサンプルを示します。
package main
import (
"fmt"
)
func hoge() {
i := 1
fmt.Printf("A:%v\n", i)
i = i + 1
defer fmt.Printf("B:%v\n", i)
i = i + 1
fmt.Printf("C:%v\n", i)
i = i + 1
defer fmt.Printf("D:%v\n", i)
i = i + 1
fmt.Printf("E:%v\n", i)
}
func main() {
fmt.Printf("main start\n")
hoge()
fmt.Printf("main end\n")
}
実行結果
main start
A:1
C:3
E:5
D:4
B:2
main end
実行結果を見ると以下のことが分かります。
-
defer
を書いた場所では関数が呼ばれていない - 関数の終了時に
defer
を書いた順番とは逆順に実行されている - 変数
i
がdefer
を書いた場所での値になっている
C++
に慣れている方ならクラスのデストラクタをイメージしていただければよいです。
これをC#
でも実現してみます。
(先に書いておきますが実用的ではありません。あくまで同じようなことを実現するだけです。)
実装
public class Deferrable
{
private List<Action> actions = new List<Action>();
public static void Execute(Action<Deferrable> action)
{
var d = new Deferrable();
try
{
action(d);
}
finally
{
d.ExecuteAll();
}
}
private Deferrable()
{
}
public void Defer(Action action)
{
this.actions.Add(action);
}
public void Defer<T>(Action<T> action, T arg)
{
this.actions.Add(() => action(arg));
}
private void ExecuteAll()
{
for (var i = this.actions.Count - 1; i >= 0; i--)
{
this.actions[i]();
}
}
}
使用例
サンプル
Deferrable.Execute(d =>
{
var index = 1;
Console.WriteLine($"A:{index++}");
d.Defer(_index => Console.WriteLine($"B:{_index}"), index++);
Console.WriteLine($"C:{index++}");
d.Defer(_index => Console.WriteLine($"D:{_index}"), index++);
Console.WriteLine($"E:{index}");
});
実行結果
A:1
C:3
E:5
D:4
B:2
解説
実装を見ればとてもシンプルなのであまり書くことはありませんがポイントはDefer<T>
とExecuteAll
でしょうか。
Defer<T>
は引数にアクションだけではなく引数も受け取ります。
これによってDefer<T>
を呼び出したときの値をコピーすることができます。
ExecuteAll
ではDefer
を呼び出した順番と逆順になるようにアクションを実行しています。
まとめ
C#
でもdefer
っぽいものを作成することはできました。
Deferrable
やList
などといった一時オブジェクトが作成されてしまうためノーコストとは呼べません。
(golang
のdefer
もdefer
を使用せずに書いた時と比べて完全にノーコストというわけではありませんがこの実装よりかは圧倒的に低いコストです。)