前回は動的にクラスを作りました。

プロパティに属性をつけようって話です。

これをすれば、Validationを動的に設定したクラスが作れるようになると。

わおって感じですね。




属性の設定はPropertyBuilder.SetCustomAttribute( CustomAttributeBuilder ) を使用します。

引数のCustomAttributeBuilderには属性クラスのコンストラクタとか諸々を指定。

ここがインスタンス設定するとアレマ!ってな具合にやってくれれば幸せなんですけど、

そこまではしてくれない模様。

冷静に考えれば、属性のインスタンス作るときのコンストラクタ何使えと!状態になるからでしょうね。


CustomAttributeBuilderのコンストラクタはこんな です。


ConstructorInfo Type.GetConstructor( Type[] ) で取得します。

後は、CustomAttributeBuilderのコンストラクタに引数と、プロパティとフィールドの情報を渡してやればいい感じです。


はい、これで属性の設定が完了です。

以下サンプルコード


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;

namespace MyUtil
{
    /// <summary>
    /// <para>動的データクラス生成クラス</para>
    /// <para>ILGeneratorを用いて動的にアセンブリを生成します</para>
    /// <remarks>
    /// <para>ネット上の文献を参考に作成</para>
    /// <para>URL : http://d.hatena.ne.jp/Horiuchi_H/20081109/1226214664 </para>
    /// </remarks>
    /// </summary>
    public class DynamicClass
    {
        /// <summary>
        /// <para>動的クラス規定クラス</para>
        /// </summary>
        public abstract class Base : INotifyPropertyChanged
        {
            #region INotifyPropertyChanged メンバ
            /// <summary></summary>
            public event PropertyChangedEventHandler PropertyChanged;
            #endregion
            /// <summary>プロパティを取得します。</summary>
            /// <returns>プロパティ名の配列</returns>
            public abstract string[] GetProperties();
            /// <summary>プロパティの変更通知を行います。</summary>
            /// <param name="name">対象プロパティ名</param>
            public void OnPropertyChanged( string name )
            {
                var h = PropertyChanged;
                if ( h != null )
                {
                    h( this, new PropertyChangedEventArgs( name ) );
                }
            }
            /// <summary>
            /// <para>値の検証を行います。</para>
            /// <para>検証の条件はプロパティの属性で指定されている物です。</para>
            /// </summary>
            /// <param name="obj">検証のオブジェクト</param>
            /// <param name="value">検証を行う値</param>
            /// <param name="MemberName">対象のプロパティ名</param>
            public void ValidateProperty( object obj, object value, String MemberName)
            {
                var ctx = new ValidationContext( obj, null, null )
                {
                    MemberName = MemberName
                };
                Validator.ValidateProperty(value, ctx);
            }
        }

        /// <summary>プロパティの情報クラス</summary>
        public class PropertyInfo
        {
            /// <summary>プロパティの型を設定</summary>
            public Type PropertyType { get; set; }
            /// <summary>属性情報</summary>
            public class AttributeInfo
            {
                /// <summary>属性の型</summary>
                public Type AttributeType { get; set; }
                /// <summary>コンストラクタの引数</summary>
                private List<object> ConstructorParams_ = new List<object();
                /// <summary>コンストラクタの引数</summary>
                public List<object> ConstructorParams 
                {
                    get { return ConstructorParams_; }
                    set { ConstructorParams_ = value; }
                }
                /// <summary>プロパティの設定値</summary>
                private Dictionary<String, object> AttributeProperties_
                    = new Dictionary<String, object>();
                /// <summary>プロパティの設定値</summary>
                public Dictionary<String, object> AttributeProperties
                {
                    get { return AttributeProperties_; }
                    set { AttributeProperties_ = value; }
                }
            }
            private List<AttributeInfo> AttributeInfoList_ = new List<AttributeInfo>();
            /// <summary>
            /// 
            /// </summary>
            public List<AttributeInfo> AttributeInfoList 
            {
                get { return AttributeInfoList_; }
                set { AttributeInfoList_ = value; }
            }
        }
        #region 定数

        /// <summary>
        /// <para>規定のクラス名</para>
        /// </summary>
        private const string DEFALT_CLASS_NAME = "UserData";

        #endregion
        #region フィールド

        /// <summary>
        /// <para>生成クラス&lt;/para>
        /// </summary>
        private Type Type_;

        #endregion

        #region プロパティ

        /// <summary>
        /// <para>生成クラス</para>
        /// </summary>
        public Type Type { get { return Type_; } private set { Type_ = value; } }

        #endregion

        #region コンストラクタ

        /// <summary>
        /// <para>コンストラクタ</para>
        /// <para>クラス名、プロパティの型・属性を指定</para>
        /// </summary>
        /// <param name="className"></param>
        /// <param name="Properties"></param>
        public DynamicClass(string className, Dictionary<String, PropertyInfo> Properties)
        {
            this.Type = CreateType( className, Properties );
        }

        /// <summary>
        /// <para>コンストラクタ</para>
        /// <para>クラス名、プロパティの型を指定</para>
        /// </summary>
        /// <param name="className"></param>
        /// <param name="propertyDictionary"></param>
        public DynamicClass(string className, Dictionary<String, Type> propertyDictionary)
        {
            Dictionary<String, PropertyInfo> Properties
                = new Dictionary<String, PropertyInfo>();
            foreach ( var property in propertyDictionary)
            {
                Properties[ property.Key ]
                    = new PropertyInfo { PropertyType = property.Value };
            }
            this.Type = CreateType( className, Properties );
        }

        /// <summary>
        /// <para>コンストラクタ</para>
        /// <para>クラス名、プロパティの型はString固定</para>
        /// </summary>
        /// <param name="className"></param>
        /// <param name="propertyNames"></param>
        public DynamicClass(string className, string[] propertyNames)
        {
            Dictionary<String, PropertyInfo> Properties
                = new Dictionary<String, PropertyInfo>();
            foreach ( var property in propertyNames)
            {
                Properties[ property ]
                    = new PropertyInfo { PropertyType = typeof( String ) };
            }
            this.Type = CreateType( className, Properties );
        }

        /// <summary>
        /// <para>コンストラクタ</para>
        /// <para>プロパティの型・属性を指定</para>
        /// </summary>
        /// <param name="Properties"></param>
        public DynamicClass( Dictionary<String, PropertyInfo> Properties )
            : this(DEFALT_CLASS_NAME, Properties){ }

        /// <summary>
        /// <para>コンストラクタ</para>
        /// <para>プロパティの型を指定</para>
        /// </summary>
        /// <param name="propertyDictionary"></param>
        public DynamicClass(Dictionary<String, Type> propertyDictionary)
            : this(DEFALT_CLASS_NAME, propertyDictionary){ }

        /// <summary>
        /// <para>コンストラクタ</para>
        /// <para>プロパティの型はString固定</para>
        /// </summary>
        /// <param name="propertyNames"></param>
        public DynamicClass(String[] propertyNames)
            : this(DEFALT_CLASS_NAME, propertyNames){ }

        #endregion

        /// <summary>アセンブリ生成</summary>
        /// <param name="className">クラス名</param>
        /// <param name="Properties">プロパティ情報ディクショナリ</param>
        /// <returns>生成クラス</returns>
        private static Type
            CreateType(string className, Dictionary<String, PropertyInfo> Properties)
        {
            AppDomain       domain          =   AppDomain.CurrentDomain;
            AssemblyName    assemblyName    =   new AssemblyName
                                                {
                                                    Name = "TempAssembly.dll";
                                                };
            AssemblyBuilder assemblyBuilder
                =   domain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
            ModuleBuilder   moduleBuilder
                =   assemblyBuilder.DefineDynamicModule( assemblyName.Name );
            TypeBuilder     typeBuilder
                =   moduleBuilder.DefineType
                    (
                        className,
                        TypeAttributes.Public | TypeAttributes.Class,
                        typeof( Base )
                    );
            #region GetPropertiesを実装
            MethodBuilder   getPropsMethod
                =   typeBuilder.DefineMethod
                    (
                        "GetProperties";,
                        MethodAttributes.Public | MethodAttributes.Virtual,
                        typeof( string[] ),
                        Type.EmptyTypes 
                    );
            ILGenerator     getPropsIL
                =   getPropsMethod.GetILGenerator();
            getPropsIL.DeclareLocal( typeof( string[] ) );
            LoadInteger( getPropsIL, Properties.Count );
            getPropsIL.Emit( OpCodes.Newarr, typeof( string ) );
            getPropsIL.Emit( OpCodes.Stloc_0 );
            for ( int index = 0; index < Properties.Count; index++ )
            {
                getPropsIL.Emit( OpCodes.Ldloc_0 );
                LoadInteger( getPropsIL, index );
                getPropsIL.Emit( OpCodes.Ldstr, Properties.Keys.ElementAt( index ) );
                getPropsIL.Emit( OpCodes.Stelem_Ref );
            }

            getPropsIL.Emit( OpCodes.Ldloc_0 );
            getPropsIL.Emit( OpCodes.Ret );

            #endregion

            MethodAttributes    propAttr
                =   MethodAttributes.Public       |
                    MethodAttributes.SpecialName  |
                    MethodAttributes.HideBySig;
            #region プロパティを作成する 
            foreach ( var Property in Properties )
            {
                //  プライベートフィールドの作成
                FieldBuilder    nameFieldBuilder
                    =   typeBuilder.DefineField
                        (
                            Property.Key + "_";
                             Property.Value.PropertyType, FieldAttributes.Private
                        );
                //  パブリックプロパティ
                PropertyBuilder namePropertyBuilder
                    =   typeBuilder.DefineProperty
                        (
                            Property.Key                    ,
                            PropertyAttributes.HasDefault   ,
                            Property.Value.PropertyType     ,
                            null
                        );
                #region 属性の設定
                if( Property.Value.AttributeInfoList != null )
                foreach ( var AttributeInfo in Property.Value.AttributeInfoList)
                {
                    Type    AttributeType   =   AttributeInfo.AttributeType;
                    //  引数のタイプリストを生成 
                    List<Type> paramType = new List<Type>();
                    foreach ( var param in AttributeInfo.ConstructorParams)
                    {
                        paramType.Add( param.GetType() );
                    }

                    //  コンストラクタの取得 
                    ConstructorInfo con
                        =   AttributeType.GetConstructor( paramType.ToArray() );
                    if ( con == null ) continue;
                    //  インスタンス生成時に設定するプロパティの生成 
                    List<System.Reflection.PropertyInfo> namedProperties
                        =   new List<System.Reflection.PropertyInfo>();
                    List<Object>    propertyValues    =    new List<object>();
                    foreach ( var AttributeProperty in AttributeInfo.AttributeProperties )
                    {
                        //  プロパティを取得する 
                        var info    =   AttributeType.GetProperty( AttributeProperty.Key );
                        if ( info != null )
                        {
                            namedProperties.Add( info );
                            propertyValues.Add( AttributeProperty.Value );
                        }
                    }
                    //  属性の設定 
                    //  引数と設定するプロパティを指定 
                    namePropertyBuilder.SetCustomAttribute
                    (
                        new CustomAttributeBuilder
                        (
                            con                                         ,
                            AttributeInfo.ConstructorParams.ToArray( )  ,
                            namedProperties.ToArray( )                  ,
                            propertyValues.ToArray( )
                         )
                     );
                }
                #endregion

                #region Getterメソッドの作成 

                MethodBuilder   getNameMethod
                    =   typeBuilder.DefineMethod
                        (
                            "get_" + Property.Key,
                            propAttr,
                            Property.Value.PropertyType,
                            Type.EmptyTypes
                        );
                ILGenerator     getNamePropIL   =   getNameMethod.GetILGenerator();
                getNamePropIL.Emit( OpCodes.Ldarg_0 );
                getNamePropIL.Emit( OpCodes.Ldfld, nameFieldBuilder );
                getNamePropIL.Emit( OpCodes.Ret );
                #endregion
                #region Setterメソッドの作成 
                MethodBuilder   setNameMethod
                    =   typeBuilder.DefineMethod
                        (
                            "set_" + Property.Key,
                            propAttr,
                            null,
                            new Type[] { Property.Value.PropertyType }
                        );
                ILGenerator     setNamePropIL   =   setNameMethod.GetILGenerator();
                setNamePropIL.Emit( OpCodes.Nop );
                setNamePropIL.Emit( OpCodes.Ldarg_0 );
                setNamePropIL.Emit( OpCodes.Ldarg_0 );
                setNamePropIL.Emit( OpCodes.Ldarg_1 );
                //  プリミティブ型ならばBox命令を発行 
                if ( Property.Value.PropertyType.IsPrimitive )
                {
                    setNamePropIL.Emit( OpCodes.Box, Property.Value.PropertyType );
                }
                setNamePropIL.Emit( OpCodes.Ldstr, Property.Key );
                setNamePropIL.Emit
                (
                    OpCodes.Call,
                    typeof( DynamicClass.Base ).GetMethod( "ValidateProperty" )
                );
                setNamePropIL.Emit( OpCodes.Nop );
                setNamePropIL.Emit( OpCodes.Ldarg_0 );
                setNamePropIL.Emit( OpCodes.Ldarg_1 );
                setNamePropIL.Emit( OpCodes.Stfld, nameFieldBuilder);

                setNamePropIL.Emit( OpCodes.Ldarg_0 );
                setNamePropIL.Emit( OpCodes.Ldstr, Property.Key );
                setNamePropIL.Emit
                (
                    OpCodes.Call,
                    typeof( DynamicClass.Base ).GetMethod( "OnPropertyChanged" )
                );
                setNamePropIL.Emit( OpCodes.Ret );
                #endregion
                namePropertyBuilder.SetGetMethod(   getNameMethod   );
                namePropertyBuilder.SetSetMethod(   setNameMethod   );
            }
            #endregion
            Type retval = typeBuilder.CreateType();
            return retval;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="il"></param>
        /// <param name="i"></param>
        private static void LoadInteger(ILGenerator il, int i)
        {
            switch ( i )
            {
                case    0   :   il.Emit(    OpCodes.Ldc_I4_0    ); break;
                case    1   :   il.Emit(    OpCodes.Ldc_I4_1    ); break;
                case    2   :   il.Emit(    OpCodes.Ldc_I4_2    ); break;
                case    3   :   il.Emit(    OpCodes.Ldc_I4_3    ); break;
                case    4   :   il.Emit(    OpCodes.Ldc_I4_4    ); break;
                case    5   :   il.Emit(    OpCodes.Ldc_I4_5    ); break;
                case    6   :   il.Emit(    OpCodes.Ldc_I4_6    ); break;
                case    7   :   il.Emit(    OpCodes.Ldc_I4_7    ); break;
                case    8   :   il.Emit(    OpCodes.Ldc_I4_8    ); break;
                case    -1  :   il.Emit(    OpCodes.Ldc_I4_M1   ); break;
                default     :
                    if (-128 <= i && i <= 127)
                    {
                        il.Emit(OpCodes.Ldc_I4_S, i);
                    }
                    else
                    {
                        il.Emit(OpCodes.Ldc_I4, i);
                    }
                    break;
            }
        }

    }
}