none
プログラムの構造を取得するFrameworkはありますか? RRS feed

  • 質問

  • こんにちは。

    .NET frameworkで以下のようなクラスをご存知の方はいらっしゃいますか?

    プログラムの構造を取得する。(構文解析を行ってくれ、結果を返してくれる、みたいな)


    たとえば、以下のようなクラスがあったとして。。。
    class A
    {
        public void method()
        {

             int i = 0;
             string[] array = new string[5]{ "","","","",""};
             foreach( string s in array )
             {
                   if( string.IsNullOrEmpty( s ) )
                   {
                        break;
                   }
             }
        }
    }

    上記のforeachやその中のif文などの構造を解析して、
    うまいことブロック単位とかに分けてくれるような。


    Visual Studio のコードスニペットとか、コンパイラとかは、おそらく内部で上記のようなことをやっていると思うのですが、
    それらはFrameworkとしては提供されていないのでしょうか?

    もしご存知でしたらご教授お願いいたします。


    koki
    2009年10月18日 3:08

回答

  • 佐祐理 さま

    早速のご返信ありがとうございます。

    なるほど、やはり提供されていないのですね。

    私も、コンパイル->アセンブリ作成の過程で、何かやっているのでは?と思い、
    CSharpCodeProviderを探していたところでした。

    ・・・・となると、
    ここはガリガリ書くしかない、ということになりますねぇ。

    foreachをLINQに(置き換えられるところは)置き換えると、パフォーマンスは約2倍良くなることがわかったので、(といっても、0.0002秒が0.0001秒になるぐらいでしたが)
    それらをforeach -> LINQ 一括変換ツールを作ろうとしているところです。
    koki
    • 回答としてマーク Koki Shimizu 2009年10月18日 13:56
    2009年10月18日 4:04
  • var query = m_array.Where(x => x.Equals("100"));
    だけではIEnumerable<string>を作ってるだけだから、速いのはあたりまえです。
    しかも③は結果の格納までやったのだから①②もquery.ToList<string>();までやらないと比較になってません。
    Ienumerableはアクセスしなければ意味のないオブジェクトなので、この結果を見てLinqが速いと考えるのは早計だと思います。

    #列挙取得という言葉を「IEnumerableを取得する」という意味で使っているのだとしても、速くなってません

    class Program
    {
        static void Main(string[] args)
        {
            string[] array = new string[10000];
            for (int i = 0; i < array.Length; i++)
            {
                array[i] = (i + 1).ToString();
            }
    
            System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
            List<string> items;
            StringBuilder sb = new StringBuilder();
    
            sb.AppendLine("*** (1) Linqで準備しただけ ***");
            for (int i = 0; i < 10; i++)
            {
                stopwatch.Reset();
                stopwatch.Start();
                var query = array.Where(x => string.Equals(x , "10000"));
                IEnumerator<string> ienum = query.GetEnumerator();
                stopwatch.Stop();
                sb.AppendLine(stopwatch.Elapsed.ToString());
            }
    
            sb.AppendLine("*** (2) Linqで結果まで取得1 ***");
            for (int i = 0; i < 10; i++)
            {
                stopwatch.Reset();
                stopwatch.Start();
                var query = array.Where(x => string.Equals(x , "10000"));
                items = query.ToList<string>();
                stopwatch.Stop();
                sb.AppendLine(stopwatch.Elapsed.ToString());
            }
    
            sb.AppendLine("*** (3) Linqで結果まで取得2 ***");
            for (int i = 0; i < 10; i++)
            {
                stopwatch.Reset();
                stopwatch.Start();
                var query = from x in array where string.Equals(x , "10000") select x;
                items = query.ToList<string>();
                stopwatch.Stop();
                sb.AppendLine(stopwatch.Elapsed.ToString());
            }
    
            sb.AppendLine("*** (4) forのindexアクセス***");
            for (int i = 0; i < 10; i++)
            {
                stopwatch.Reset();
                stopwatch.Start();
                items = new List<string>();
                for (int idx = 0; idx < array.Length; idx++)
                {
                    string str;
                    str = array[idx];
                    if (string.Equals(str , "10000")) items.Add(str);
                }
                stopwatch.Stop();
                sb.AppendLine(stopwatch.Elapsed.ToString());
            }
    
            sb.AppendLine("*** (5) foreach ***");
            for (int i = 0; i < 10; i++)
            {
                stopwatch.Reset();
                stopwatch.Start();
                items = new List<string>();
                foreach (string str in array)
                {
                    if (string.Equals(str , "10000")) items.Add(str);
                }
                stopwatch.Stop();
                sb.AppendLine(stopwatch.Elapsed.ToString());
            }
    
            sb.AppendLine("*** (6-1) (2)でやっていることを自前で処理1 ***");
            for (int i = 0; i < 10; i++)
            {
                stopwatch.Reset();
                stopwatch.Start();
                items = new List<string>();
                foreach (string str in GetStringEnumerable(array))
                {
                    items.Add(str);
                }
                stopwatch.Stop();
                sb.AppendLine(stopwatch.Elapsed.ToString());
            }
            sb.AppendLine("*** (6-2) (2)でやっていることを自前で処理2 ***");
            for (int i = 0; i < 10; i++)
            {
                Predicate<string> where10000 = delegate(string str) { return string.Equals(str , "10000"); };
                stopwatch.Reset();
                stopwatch.Start();
                items = new List<string>();
                foreach (string str in GetStringEnumerable(array , where10000))
                {
                    items.Add(str);
                }
                stopwatch.Stop();
                sb.AppendLine(stopwatch.Elapsed.ToString());
            }
    
            sb.AppendLine("*** 7 (1)でやっていることを自前でやっただけ ***");
            for (int i = 0; i < 10; i++)
            {
                Predicate<string> where10000 = delegate(string str) { return string.Equals(str , "10000"); };
                stopwatch.Reset();
                stopwatch.Start();
                IEnumerable<string> ienum = GetStringEnumerable(array , where10000);
                stopwatch.Stop();
                sb.AppendLine(stopwatch.Elapsed.ToString());
            }
    
            Console.Write(sb.ToString());
        }
    
    
        public static IEnumerable<string> GetStringEnumerable(string[] array , Predicate<string> predicate)
        {
            foreach (string str in array) if (predicate(str)) yield return str;
        }
    
        public static IEnumerable<string> GetStringEnumerable(string[] array)
        {
            foreach (string str in array) if (string.Equals(str , "10000")) yield return str;
        }
    }
     


    • 回答としてマーク Koki Shimizu 2009年10月18日 13:56
    2009年10月18日 12:19

すべての返信

  • まず、.NET FrameworkではCodeDom という形で構文解析結果を持つことができます。
    しかしこの中にforeachなどはなく、whileループに展開して扱わざるを得ません。またC# 3.0のlambda式なども表現できなかったと思います。(Expressionになってしまう。)

    それからこの構文解析を行うCSharpCodeProviderクラス にParse()メソッドがありますが、これも実装されていないのでC#言語をソースから解析する手段は提供されていなかったと思います。
    2009年10月18日 3:56
  • 佐祐理 さま

    早速のご返信ありがとうございます。

    なるほど、やはり提供されていないのですね。

    私も、コンパイル->アセンブリ作成の過程で、何かやっているのでは?と思い、
    CSharpCodeProviderを探していたところでした。

    ・・・・となると、
    ここはガリガリ書くしかない、ということになりますねぇ。

    foreachをLINQに(置き換えられるところは)置き換えると、パフォーマンスは約2倍良くなることがわかったので、(といっても、0.0002秒が0.0001秒になるぐらいでしたが)
    それらをforeach -> LINQ 一括変換ツールを作ろうとしているところです。
    koki
    • 回答としてマーク Koki Shimizu 2009年10月18日 13:56
    2009年10月18日 4:04
  • 0.0002秒とか0.0001秒とかの測定精度はどれぐらいか把握しているのでしょうか? また測定のためのオーバーヘッドは? ほんとうに速くなるんですか?
    2009年10月18日 4:06
  • 佐祐理

    測定精度と仰られると・・・
    申し訳ございません、おそらく把握していないと思います。

    そして、私も再確認のため、いまいちど実験を行ったところ、列挙を作成するだけなら、LINQのほうが速いっぽいです。

    以下の実験を行いました。
    -------------------------------------------------------------------

    stringの配列
    m_array に1~100までの数値文字列が格納されています。(要素数が100です)
    この配列から、文字列が"100"のものを列挙取得するというプログラムを組みます。

    ①LINQのメソッド構文により、結果の列挙を取得する
    var query = m_array.Where(x => x.Equals("100"));

    ②LINQのノーマル構文により、結果の列挙を取得する
    var query = from x in m_array
                                where x.Equals( "100" )
                                select x;

    ③for文で、100回まわす
    Collection<string> items= new Collection<string>();
    for (int i = 0; i < m_array.Length; i++)
    {
        if (m_array[i].Equals("100"))
        {
            items.Add(m_array[i]);
        }
    }

    上記①、②,③をそれぞれ10回ループして結果を得ました。
    ①の結果
    00:00:00.0025221
    00:00:00.0000030
    00:00:00.0000013
    00:00:00.0000025
    00:00:00.0000016
    00:00:00.0000013
    00:00:00.0000016
    00:00:00.0000016
    00:00:00.0000016
    00:00:00.0000016

    ②の結果
    00:00:00.0024944
    00:00:00.0000030
    00:00:00.0000016
    00:00:00.0000019
    00:00:00.0000016
    00:00:00.0000013
    00:00:00.0000016
    00:00:00.0000016
    00:00:00.0000016
    00:00:00.0000016

    ③の結果
    00:00:00.0019052
    00:00:00.0000058
    00:00:00.0000039
    00:00:00.0000041
    00:00:00.0000041
    00:00:00.0000041
    00:00:00.0000039
    00:00:00.0000039
    00:00:00.0000039
    00:00:00.0000039

    単純に、上記①、②、③の比較だと、
    ①、②のほうが、③よりも倍早いです。
    ただ、最初の1回は遅いですね。これはLINQを使用する際のアセンブリ作成が絡んでいるのでしょうか?そこまではわかりませんでした。

    ただ、ここで問題がありました。
    ①、②の場合に、”100”のあった個数をカウントしたい場合は、query.Count()を呼ばなくてはなりません。
    もしquery.Count()を呼び出した場合は、①、②は③の倍の時間がかかりました。

    query.Count()を呼び出した際の計測値

    00:00:00.0044206
    00:00:00.0000164
    00:00:00.0000209
    00:00:00.0000094
    00:00:00.0000094
    00:00:00.0000092
    00:00:00.0000094
    00:00:00.0000094
    00:00:00.0000092
    00:00:00.0000094


    00:00:00.0038046
    00:00:00.0000145
    00:00:00.0000092
    00:00:00.0000092
    00:00:00.0000092
    00:00:00.0000089
    00:00:00.0000089
    00:00:00.0000089
    00:00:00.0000089
    00:00:00.0000092


    00:00:00.0032073
    00:00:00.0000072
    00:00:00.0000170
    00:00:00.0000044
    00:00:00.0000041
    00:00:00.0000039
    00:00:00.0000041
    00:00:00.0000039
    00:00:00.0000041
    00:00:00.0000039


    上記実験からの結果は、
    単純な列挙取得であれば、LINQのほうが早い。
    しかし、LINQの列挙結果にアクセスを行う場合は、そのパフォーマンスは悪い、
    ということがわかりました。

    ただ、LINQを使用したほうが、コードはスッキリしますので、
    置き換えツールは暇があったら作ってみようかと思います。




    koki
    2009年10月18日 5:21
  • var query = m_array.Where(x => x.Equals("100"));
    だけではIEnumerable<string>を作ってるだけだから、速いのはあたりまえです。
    しかも③は結果の格納までやったのだから①②もquery.ToList<string>();までやらないと比較になってません。
    Ienumerableはアクセスしなければ意味のないオブジェクトなので、この結果を見てLinqが速いと考えるのは早計だと思います。

    #列挙取得という言葉を「IEnumerableを取得する」という意味で使っているのだとしても、速くなってません

    class Program
    {
        static void Main(string[] args)
        {
            string[] array = new string[10000];
            for (int i = 0; i < array.Length; i++)
            {
                array[i] = (i + 1).ToString();
            }
    
            System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
            List<string> items;
            StringBuilder sb = new StringBuilder();
    
            sb.AppendLine("*** (1) Linqで準備しただけ ***");
            for (int i = 0; i < 10; i++)
            {
                stopwatch.Reset();
                stopwatch.Start();
                var query = array.Where(x => string.Equals(x , "10000"));
                IEnumerator<string> ienum = query.GetEnumerator();
                stopwatch.Stop();
                sb.AppendLine(stopwatch.Elapsed.ToString());
            }
    
            sb.AppendLine("*** (2) Linqで結果まで取得1 ***");
            for (int i = 0; i < 10; i++)
            {
                stopwatch.Reset();
                stopwatch.Start();
                var query = array.Where(x => string.Equals(x , "10000"));
                items = query.ToList<string>();
                stopwatch.Stop();
                sb.AppendLine(stopwatch.Elapsed.ToString());
            }
    
            sb.AppendLine("*** (3) Linqで結果まで取得2 ***");
            for (int i = 0; i < 10; i++)
            {
                stopwatch.Reset();
                stopwatch.Start();
                var query = from x in array where string.Equals(x , "10000") select x;
                items = query.ToList<string>();
                stopwatch.Stop();
                sb.AppendLine(stopwatch.Elapsed.ToString());
            }
    
            sb.AppendLine("*** (4) forのindexアクセス***");
            for (int i = 0; i < 10; i++)
            {
                stopwatch.Reset();
                stopwatch.Start();
                items = new List<string>();
                for (int idx = 0; idx < array.Length; idx++)
                {
                    string str;
                    str = array[idx];
                    if (string.Equals(str , "10000")) items.Add(str);
                }
                stopwatch.Stop();
                sb.AppendLine(stopwatch.Elapsed.ToString());
            }
    
            sb.AppendLine("*** (5) foreach ***");
            for (int i = 0; i < 10; i++)
            {
                stopwatch.Reset();
                stopwatch.Start();
                items = new List<string>();
                foreach (string str in array)
                {
                    if (string.Equals(str , "10000")) items.Add(str);
                }
                stopwatch.Stop();
                sb.AppendLine(stopwatch.Elapsed.ToString());
            }
    
            sb.AppendLine("*** (6-1) (2)でやっていることを自前で処理1 ***");
            for (int i = 0; i < 10; i++)
            {
                stopwatch.Reset();
                stopwatch.Start();
                items = new List<string>();
                foreach (string str in GetStringEnumerable(array))
                {
                    items.Add(str);
                }
                stopwatch.Stop();
                sb.AppendLine(stopwatch.Elapsed.ToString());
            }
            sb.AppendLine("*** (6-2) (2)でやっていることを自前で処理2 ***");
            for (int i = 0; i < 10; i++)
            {
                Predicate<string> where10000 = delegate(string str) { return string.Equals(str , "10000"); };
                stopwatch.Reset();
                stopwatch.Start();
                items = new List<string>();
                foreach (string str in GetStringEnumerable(array , where10000))
                {
                    items.Add(str);
                }
                stopwatch.Stop();
                sb.AppendLine(stopwatch.Elapsed.ToString());
            }
    
            sb.AppendLine("*** 7 (1)でやっていることを自前でやっただけ ***");
            for (int i = 0; i < 10; i++)
            {
                Predicate<string> where10000 = delegate(string str) { return string.Equals(str , "10000"); };
                stopwatch.Reset();
                stopwatch.Start();
                IEnumerable<string> ienum = GetStringEnumerable(array , where10000);
                stopwatch.Stop();
                sb.AppendLine(stopwatch.Elapsed.ToString());
            }
    
            Console.Write(sb.ToString());
        }
    
    
        public static IEnumerable<string> GetStringEnumerable(string[] array , Predicate<string> predicate)
        {
            foreach (string str in array) if (predicate(str)) yield return str;
        }
    
        public static IEnumerable<string> GetStringEnumerable(string[] array)
        {
            foreach (string str in array) if (string.Equals(str , "10000")) yield return str;
        }
    }
     


    • 回答としてマーク Koki Shimizu 2009年10月18日 13:56
    2009年10月18日 12:19
  • 上記実験からの結果は、
    単純な列挙取得であれば、LINQのほうが早い。
    しかし、LINQの列挙結果にアクセスを行う場合は、そのパフォーマンスは悪い、
    ということがわかりました。

    ただ、LINQを使用したほうが、コードはスッキリしますので、
    置き換えツールは暇があったら作ってみようかと思います。
    既に指摘されていますが、そんなことよりもっと学ぶべきものがいろいろあると思います。

    class A
    {
        public void method()
        {
             //int i = 0;
             var array = new[]{ "","","","",""};
             Array.Exists( array, string.IsNullOrEmpty );
        }
    }

    別にこれがいいとは言いませんが。
    測定精度と言ったのは、0.00015秒と0.00014秒が四捨五入されて2倍違うように見えることもあります、と。
    2009年10月21日 4:23