C#での動的プログラミング

C#4.0から動的プログラミングがサポートされるようになりました。ちゃんと理解できてますか?この動的プログラミング、今回は80:20の法則に従って20を説明して80をわかった気になってもらおうと思います。

動的プログラミング、色々ありますが”dynamic” と “ExpandoObject” この2つをカバーすれば実際使うケースの80%をカバーできるのではないかと思います。

 

1.dynamicキーワード

C#3.0からvarという匿名型が出ました、これはコンパイル時に型を推定するものです。LINQでselectメソッドを使うと匿名クラスがすぐできるのでこれを受けるために(かどうかは知らないけど)varが生まれました。対して、dynamicは実行時に型を推定します、サンプルです。

TestDynamicKeyword.cs

namespace DynamicTestConsoleApp
{
    using System;
    using System.Text;
    public class TestDynamicKeyword
    {
        public static void Sample()
        {
            // dynamicキーワードのテスト
            // objは「ダイナミック型」として実行時に型情報を評価される
            dynamic str = "This type is string";

            // 1.存在しないプロパティにアクセスしてもコンパイルエラーにはならない
            try
            {
                str.CallNonExistingMethod();
            }
            catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException e)
            {
                // ただし、存在しない型なので実行時に例外がスローされる
                Console.WriteLine("1: " + e.Message);
            }

            // 2.同一オブジェクトに、別の型のオブジェクトも代入できる
            str = new StringBuilder("This type is stringbuilder.");

            // 3.もちろん型名はちゃんとその型になっている
            Console.WriteLine("3: " + str.GetType());

            // 4.varはコンパイル時に型を推定するが、dynamicなオブジェクトを入れると
            //   それはdynamicキーワードで受けたのと同じことになる。
            var copiedVarObject = str;
            Console.WriteLine("4: " + copiedVarObject.GetType());

            // 5.キーワードが"dynamic"で宣言されたかどうかを取得する方法はない

            // 6.object型に対するメリットは例えば下記
            object num = 10;
            //   num *= 10; <- これがコンパイルエラーになる。
            //   明示的にキャストをすればエラーにならない。
            num = (int)num * 10;
            //   dynamicならキャストも不要。
            dynamic dynamicNum = 10;
            dynamicNum *= 10;

            // 7.ただintellisenseが無効なことに注意。
        }
    }
}

dynamic1

静的/静的はそれぞれの善し悪しがあります。C#は基本的に静的なな型付けをするため、実際に上のような例でdynamicを使うメリットはありません。よく言われることですが、Pythonなど他の言語と結合する部分で使うメリットがあります。「そんな言語使わねー氏」って方にちょっとべんりかもしれない例を挙げます。

WPFアプリのとあるコード

private static object SearchTargetHierarchy(dynamic parentElement)
{
    if( !(parentElement is FramworkElement) ||
        !(parentElement is DependencyObject) ||
        !(parentElement is FrameworkContentElement))
    {
        return null;
    }
    foreach (var item in LogicalTreeHelper.GetChildren(parentElement))
    {
        // 適当にやりたいこと
        WindowsFormsHost host = item as WindowsFormsHost;
        if (host != null && host.Child != null)
        {
            return host.Child;
        }
        var result = SearchTargetHierarchy(item);
        if (result != null)
        {
            return result;
        }
    }
    return null;
}

やっていることはツリーの要素を見回ってWindowsFormsHostがあればそのChildを取得することなんですが、そんなことはどうでもいいんです。上の例でdynamicが生きてくるポイントは、LogicalTreeHelper.GetChildren()です。このメソッドは引数として、ガード節でチェックしている3つの型を取ります。そのため3つのうちどれかとして呼びたいだけであっても具体的な型にキャストして呼ばないといけません、objectとして受けることもできませんし、一意にキャストするのもダメです。その代案として型チェックをした上で(=型の安全性を確認した上で)、dynamicで受けると不要なキャストと繰り返しコードがすっきりできます。

 

2.ExpandoObjectクラス

“Expando”は造語だそう、「拡張可能なオブジェクト」と思えば十分でしょう。サンプルです。

namespace DynamicTestConsoleApp
{
    using System;
    public class TestExpandoObject
    {
        public static void Sample()
        {
            // ExpandoObject型のテスト
            // 動的クラスを拡張するためのクラスであり、プロパティ追加などが可能
            var item = new System.Dynamic.ExpandoObject();

            // 1.上記varで受けるとitemはExpandoObject型なので、Aなどというプロパティはない。
            //   そのため、下記はコンパイルエラーになる。
            //item.A = "Property A";

            // 2. ExpandoObjectを使う場合はdynamicで受けないとメリットがでない
            dynamic obj = new System.Dynamic.ExpandoObject();
            obj.A = "Property A";
            Console.WriteLine("2: " + obj.A);

            // 3-1. メソッドも追加が可能
            obj.Method = new Action(() => Console.WriteLine("3-1: This is method call without argment"));
            obj.Method();

            // 3-2. 引数ももちろん取れます
            obj.MethodWithArg = new Action<int, int>((a, b) => Console.WriteLine("3-2 " + a + b));
            obj.MethodWithArg(1, 2);

            // 4.イベントも可能
            //   但し、初回アクセスから+=はNG。
            //obj.Event += new EventHandler(test); 実行時エラーになる
            obj.Event = new EventHandler(test);
            obj.Event += new EventHandler(test2);
            obj.Event(null, EventArgs.Empty);
        }

        static void test(object sender, EventArgs e)
        {
            Console.WriteLine("4-1: This is test1.");
        }
        static void test2(object sender, EventArgs e)
        {
            Console.WriteLine("4-2: This is test2.");
        }
    }
}

dynamic2

dynamicは動的な型を受け付ける仕組みですが、動的に型が作れないならそれはすべてvarで受けたらいい話。なのでdynamicとExpandoObjectはセットで出てきますが、ちゃんと違いを意識しましょう。ExpandoObject は内部的にはディショナリとして動的処理を作り出しますが、そんなことは使う上ではあまり知らなくて良いかと思います。

子供が夜泣きしたのでこれで;;

広告

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中