T4 Text TemplateによるViewModelクラスの生成 その3

さて、ViewModelの自動生成の続きだ。

おさらいになるが、自動生成するViewModelは対象のネームスペース、クラスを決定しなくてはならない。そして生成するプロパティ(名前、型、デフォルト値)を決めたい。そのためにはテンプレート(ジェネレータ)側に何らかの方法でそれら(型、プロパティ)のメタデータを引き渡す必要がある。

ネームスペースは対象となるクラスと同じ名前とし、クラス名も対象となるクラス名に任意のサフィクス(_Generated)を追加した名前にすれば良いのでパラメタライズの必要すらないだろう。残るはプロパティのメタデータだ。

さて、プロパティのメタデータをテンプレート(ジェネレータ)に引き渡すにはどんな方法があるだろうか。

好みもあるだろうが、私は最後が一番好きでありJava APTでコード生成した時もアノテーションを使ったので、今回もその方法でいくことにする。Javaと違うのはアノテーションではなく属性(Attribute)と呼ぶことと、インタフェースではなく具象クラスで定義する必要があることだ。 また、対象要素(クラス、フィールド、プロパティ)に対して複数の属性を記述できるのもJavaアノテーションとはひと味違う所だ。

PropertyDecl.cs
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
    public class PropertyDecl : Attribute
    {
        public string name;
        public Type type;
        public object defaultValue;
        public bool notifyPropertyChanged = true;

        public PropertyDecl() 
            : base()
        {
        }
        public PropertyDecl(string name, Type type, object defaultValue = null)
            :this()
        {
            this.name = name;
            this.type = type;
            this.defaultValue = defaultValue;
        }
    }

このPropertyDeclクラスにより、生成するViewModelクラスに対してプロパティを追加するのだが、その際に

    • プロパティ名
    • プロパティ型
    • デフォルト値
    • プロパティ変更通知の有無

これらを外部から設定できるようにする。

  • 使用例

以下のように生成するクラスのプロトタイプに対して、必要なプロパティの数だけ属性を記述する。

MyViewModel.cs
namespace DataBindApp1.ViewModel
{
    [PropertyDecl("Name", typeof(String), "")]
    [PropertyDecl( name="Age", type=typeof(int), defaultValue = 20)]
    public partial class MyViewModel
    {}
}

想定としては上記のように属性を記述した場合、以下のように自動生成されて欲しい訳だ。

MyViewModel_Generated.cs
namespace DataBindApp1.ViewModel
{
    public partial class MyViewModel: ViewModel
    {
        #region Name

        private String _name  ;
        public String Name
        {
            get { return _name ; }
            set 
            { 
                if ( _name == value ) 
                    return;
                _name = value; 
                RaisePropertyChanged(() => Name);
            }
        }

        #endregion
        #region Age

        private int _age = 20 ;
        public int Age
        {
            get { return _age ; }
            set 
            { 
                if ( _age == value ) 
                    return;
                _age = value; 
                RaisePropertyChanged(() => Age);
            }
        }

        #endregion
    }
}

.NET C#のPartial typeのお陰でJavaでは不可能だった同一クラスを複数のファイルに跨って定義することが可能であり、今回のようにソースコードを生成するユースケースにはすごく便利な機能だ。