Python式を使ったオブジェクトのインジェクション

最近オープンソースで作り始めた拙作のSpring.Extensionsライブラリ。以下のエントリで書いたコードを元にして作り始めたのだが、
Spring.NETでカスタム属性を使ったインジェクションを実現する - Architect Life

この機能を実装したら他に実装したい機能が思いつかなかった。*1
とりあえず仕方がないので、以下のエントリで書いたコードも入れる事にした。

http://d.hatena.ne.jp/coma2n/20071216/1197781194

他にも何かないか考えていて、前々からIronPythonを言語内言語として組み込みたかった事を思い出して、それならとPython式をインジェクションに使えるようにする機能を入れる事にした。

イメージ
[Object]
class Hoge {
    private int[] intValues;
    // int型の配列をインジェクションする
    [Injection("py:[0, 1, 2, 3, 4]")]
    public int[] IntValues {
        get { return intValues; }
        set { intValues = value; }
    }
}

Injection属性のコンストラクタで通常では文字列や数値などのプリミティブな型しか使えないけど*2、py:という接頭語を付ける事で、その後がPython式として評価され、その式の結果がプロパティにインジェクションされる。

Python式を導入する事で配列やマップなどの定義が楽になる。*3

これを実現するために、まずPython式を評価するためのクラスを作る。

PythonEvaluator.cs

using System;
using System.IO;
using System.Reflection;
using System.Diagnostics;

using IronPython.Hosting;

public static class PythonEvaluator {
    /// <summary>
    /// Pythonエンジン
    /// </summary>
    private static PythonEngine pyEngine;

    public static object Evaluate(string expression) {
        if(pyEngine == null) {
            pyEngine = new PythonEngine();
            pyEngine.LoadAssembly(Assembly.Load("Spring.Core"));

            using(Stream stm = Assembly.GetExecutingAssembly().GetManifestResourceStream(
                "Spring.Extensions.springextensions.py")) {
                pyEngine.Execute(new StreamReader(stm).ReadToEnd());
            }
        }
        return pyEngine.Evaluate(expression);
    }
}

やってる事は文字列として渡したPython式をPythonEngineクラスを使って評価して、その結果を返すだけ。
途中でSpring.Extensions.springextensions.pyという埋め込みリソースを読み込んで、ファイルとして実行しているが、これはPython式からコンテナのオブジェクト参照を取り出すための関数を定義しているファイルを読み込むため。

そのファイルの内容は以下

springextensions.py
from Spring.Objects.Factory.Config import RuntimeObjectReference

def ref(name):
    return RuntimeObjectReference(name)

defという関数を定義し、引数として渡された名前でRuntimeObjectReferenceを初期化して返している。

こうすることで、以下のようにPython式からオブジェクト参照を取り出せる。

イメージ
[Object]
class Hoge {
    private object value;
    // hogeというオブジェクトがコンテナに登録されている前提
    [Injection("py:ref('hoge')")]
    public object Value {
        get { return value; }
        set { this.value = value; }
    }
}

あとはクラスにマークされた属性からオブジェクト定義に変換するクラスであるAnnotationObjectParserでInjection属性の値がPython式であれば、PythonEvaluatorを使ってその式を評価するようにする。

AnnotationObjectParser.cs
private static IEnumerable<PropertyValue> GetInjectionProperties(Type tp) {
    foreach(PropertyInfo propInfo in tp.GetProperties()) {
        if(!propInfo.CanWrite) continue;

        object[] attrs = propInfo.GetCustomAttributes(typeof(InjectionAttribute), false);

        if(attrs.Length > 0) {
            InjectionAttribute injectAttr = (InjectionAttribute)attrs[0];

            object value = injectAttr.Value;

            if(injectAttr.IsObjectReference) {
                value = new RuntimeObjectReference(value.ToString());
            } else if(injectAttr.IsPythonExpression) {
            	// Python式なら
                value = PythonEvaluator.Evaluate(value.ToString());
            }
            yield return new PropertyValue(propInfo.Name, value);
        }
    }
}

これで単純なPython式なら評価する事ができるようになった。

*1:といってもまだSetterインジェクションしかできていないので、Constructorインジェクションとかもできるようにしないといけないけど

*2:ref:とするとコンテナに定義されたオブジェクトを参照する事もできる

*3:というかこれが無かったら配列とかをインジェクションできなかったorz