Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
4
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

posted at

[C#]C#でDeferする

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を書いた順番とは逆順に実行されている
  • 変数ideferを書いた場所での値になっている

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っぽいものを作成することはできました。
DeferrableListなどといった一時オブジェクトが作成されてしまうためノーコストとは呼べません。
(golangdeferdeferを使用せずに書いた時と比べて完全にノーコストというわけではありませんがこの実装よりかは圧倒的に低いコストです。)

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
4
Help us understand the problem. What are the problem?