前回は動的にクラスを作りました。
プロパティに属性をつけようって話です。
これをすれば、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>生成クラス</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; } } } }