2016年7月8日金曜日

C#クロージャのコストについて

プログラマ向けのテーマではないです. プログラムも行うデザイナ向けです.
もちろん, ちょっとしたツールを作るときはあなたの自由にプログラミングを楽しむべきです.

Visual Studio 2013 .Net Framework 4.5でコンパイルしました.
実験にはILSPYを使用しました.

URIは失念しましたが, このような実験を行った先人はいます.
疑問を持ったら, せっかく便利なツールがあるのですから, まず自分でやってみるべきです.

以下のプログラムは,
namespace Closure
{
    class ClosureTest
    {
        int x_;

        public static System.Action getFunc1()
        {
            int x=0;
            return ()=>{System.Console.WriteLine("{0}", x);};
        }

        public void func2()
        {
            System.Console.WriteLine("{0}", x_);
        }

        public void proc0()
        {
            int x=0;
            System.Action action = ()=>{System.Console.WriteLine("{0}", x);};
            action();
        }

        public void proc1()
        {

            System.Action action = getFunc1();
            action();
        }

        public void proc2()
        {
            System.Action action = func2;
            action();
        }
    };
}

コンパイル結果をILSPYで逆コンパイルすると, 以下のようになりました.

無名関数外のスコープの変数をキャプチャするために, コンパイラがクラスを作成します.
全く同じように見えるクロージャであっても別のクラスが作成されます.
おそらく, 高級な機能にはあなたが想像している以上のコストが隠されています.
このコンパイラの挙動をそのままプログラマにやらせるのが,
初期Javaの無名クラスと思えばわかりやすいです.

コンパイラによってはもう少し最適化されるようですが,
おおまかには, このようにクロージャが実現されています.
namespace Closure
{
    internal class ClosureTest
    {
        [CompilerGenerated]
        private sealed class <>c__DisplayClass1
        {
            public int x;

            public void b__0()
            {
                Console.WriteLine("{0}", this.x);
            }
        }

        [CompilerGenerated]
        private sealed class <>c__DisplayClass4
        {
            public int x;

            public void b__3()
            {
                Console.WriteLine("{0}", this.x);
            }
        }

        private int x_;

        public static Action getFunc1()
        {
            ClosureTest.<>c__DisplayClass1 <>c__DisplayClass = new ClosureTest.<>c__DisplayClass1();
            <>c__DisplayClass.x = 0;
            return new Action(<>c__DisplayClass.b__0);
        }

        public void func2()
        {
            Console.WriteLine("{0}", this.x_);
        }

        public void proc0()
        {
            ClosureTest.<>c__DisplayClass4 <>c__DisplayClass = new ClosureTest.<>c__DisplayClass4();
            <>c__DisplayClass.x = 0;
            Action action = new Action(<>c__DisplayClass.b__3);
            action();
        }

        public void proc1()
        {
            Action action = ClosureTest.getFunc1();
            action();
        }

        public void proc2()
        {
            Action action = new Action(this.func2);
            action();
        }
    }
}

おまけ. Enumeratorはどうなるのか.
namespace Enumerator
{
    public struct ToFunctor0
    {
        public delegate void Func();

        public static Functor Do(Func func)
        {
            return new Functor(func, null);
        }
    };

    public struct ToFunctor1
    {
        public delegate void Func(T t);

        public static Functor Do(Func func, params System.Object[] args)
        {
            return new Functor(func, args);
        }
    };

    public struct Functor : System.Collections.IEnumerator
    {
        private bool done_;
        private System.Delegate func_;
        private System.Object[] args_;

        public Functor(System.Delegate func, params System.Object[] args)
        {
            func_ = func;
            args_ = args;
            done_ = (null == func_);
        }

        public object Current
        {
            get { return null; }
        }

        public bool MoveNext()
        {
            if(!done_) {
                func_.DynamicInvoke(args_);
                done_ = true;
            }
            return false;
        }

        public void Reset()
        {
            done_ = false;
        }
    };

    class EnumeratorTest
    {
        int x_;
        public void func0(int x)
        {
            System.Console.WriteLine("{0} {1}", x_, x);
        }

        public void proc0()
        {
            System.Collections.IEnumerator proc = ToFunctor1.Do(func0, 0);
            while(proc.MoveNext()){
            }
        }
    };
}
インターフェイスをstructが実装できるという事実について,
あまり知られていないように感じています. どうなんでしょう?
引数で配列のnewが入るのが気に入らない.
namespace Enumerator
{
    [StructLayout(LayoutKind.Sequential, Size = 1)]
    public struct ToFunctor0
    {
        public delegate void Func();

        public static Functor Do(ToFunctor0.Func func)
        {
            return new Functor(func, null);
        }
    }

    [StructLayout(LayoutKind.Sequential, Size = 1)]
    public struct ToFunctor1
    {
        public delegate void Func(T t);

        public static Functor Do(ToFunctor1.Func func, params object[] args)
        {
            return new Functor(func, args);
        }
    }


    internal class EnumeratorTest
    {
        private int x_;

        public void func0(int x)
        {
            Console.WriteLine("{0} {1}", this.x_, x);
        }

        public void proc0()
        {
            IEnumerator proc = ToFunctor1.Do(new ToFunctor1.Func(this.func0), new object[]
            {
                0
            });
            while (proc.MoveNext()){}
        }
    }
}

0 件のコメント:

コメントを投稿