通信人家园

 找回密码
 注册

只需一步,快速开始

短信验证,便捷登录

搜索

军衔等级:

  新兵

注册:2010-6-24
跳转到指定楼层
1#
发表于 2010-12-23 10:44:06 |只看该作者 |倒序浏览
背景
  我在一个项目中遇到这样一件事。一开始用户对要编辑的数据
没有多少要求,于是我用PropertyGrid来提供编辑界面,我的开
发被简化了。但是用户使用了一段时间后提出,所有对象的
属性个数必须可以动态增减,甚至在运行中。虽然他们再次表示
增减的个数不会超过5个,但是这次我选择不相信他们了,我需要
一个具有一定弹性的设计。于是每个对象会自带一个Dictionary保
存属性。我再提供配置文件来描述每种对象的属性表。到目前为止
一切OK。但是当我将这个对象赋给PropertyGrid时问题来了,这个
控件竟然不允许手动增减属性,她只接受对象的public property!

问题
  如上所述
  1、我们有一个对象,对象要编辑的属性不是它自身的property,
  而是保存在一个Dictionary里;
  2、对象属性的编辑界面PropertyGrid只接受public property;
  3、我不想自己开发编辑界面;

分析
  根据需求我们不难看出真正困扰我的其实是第3条,而这一条是
我自己强加的,用户对编辑界面的唯一要求就是简单,
PropertyGrid他们认可,Label加TextBox他们也认可,在这方面他
们其实是很可爱的。
  我现在可以放弃我自己的需求实现一个Label加TextBox的对话框
,也可以用DataGridView。其实只要放弃PropertyGrid我有很多选
择。但是我喜欢PropertyGrid,他在这个项目中正合适,而且我自
己很难实现出它的效果。
  其实说白了,我们只要把Dictionary中间的键值对变成对象的
property就行了,所有的矛盾一下归结为:如何创建一个对象,它
的property都来自Dictionary中间的键值对。这时我的脑海中闪出
一个单词“Emit”。
  Emit是一个很强大的功能,但是不太好用(大概这是一个铁律:
强大的都不好驾驭),我也就没有认真学习过。现在机会来了。
我在这里只简单介绍一下Emit的使用,感兴趣的可以深入学习,最
好能把学习成果发布到这里与大家分享。

Emit简介
  Emit是一种允许代码在运行时创建并执行代码的功能,用它创建
的代码功能有限制,但是执行效率与编译后的代码无异。提醒一下
,想使用Emit需要对DotNet的内存模型有一定的了解,更重要的是
需要了解IL的知识。下面我用一个例子说明使用Emit的大致流程,
一个HelloWorld程序;)

(这段代码来自项目exam/exam1)
Code

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection.Emit;
using System.Reflection;

namespace demo1
{
   
class Program
    {
        
static
void Main(string[] args)
        {
            
//创建程序集“DynamicAssembly”
            AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
               
new AssemblyName("DynamicAssembly")//
                , AssemblyBuilderAccess.Run //该程序集只用作运行,你还可以创建可保存的程序集
                );

            
//创建模块“DynamicModule”
            ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("DynamicModule");

            
//创建类型“DynamicClass”
            TypeBuilder typeBuilder = moduleBuilder.DefineType("DynamicClass"
                , TypeAttributes.Public
| TypeAttributes.Class //相当于 public class DynamicClass
                , null
//基类
                , null
//接口
                );

            
//为类型增加一个方法“Greet”
            MethodBuilder methodBuilder = typeBuilder.DefineMethod("Greet"
                , MethodAttributes.Public
                );

            
//获得ILGenerator对象,该对象用来为方法注入IL语言
            ILGenerator g = methodBuilder.GetILGenerator();

            
//将字符串"HelloWorld"放到堆栈顶端
            g.Emit(OpCodes.Ldstr, "HelloWorld");
            
//调用Console.WriteLine打印堆栈顶端的值
            g.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine"
                , BindingFlags.Public
| BindingFlags.Static
                ,
null
                ,
new Type[] { typeof(string) }
                ,
null));
            
//return
            g.Emit(OpCodes.Ret);

            
//创建动态类型
            Type type = typeBuilder.CreateType();
            
//实例化类型

object target = Activator.CreateInstance(type);
            
//获得Greet方法的描述
            MethodInfo mi = type.GetMethod("Greet");
            
//调用方法
            mi.Invoke(target, null);
        }
    }
}



  上面的代码先创建了一个程序集“DynamicAssembly”,然后给程序集增加
一个模块“DynamicModule”,最后在模块中创建类型“DynamicClass”。这是
使用Emit的经典步骤。在得到类型以后真正的工作才开始。上面的代码只
是为类型定义了一个函数“Greet”,然后用ILGenerator来为函数注入代码:
Console.WriteLine("HelloWorld")。

 好了,Emit就介绍到这里。下面我们来看Emit技术实现我们的动态属性对象。

动态属性对象实现
  我们需要的是一个可以把一系列键值对转换为一个类型的public property的
工具,说白了就是一个函数。
  关于这个动态类型,我们得仔细考虑:
  1)它的public property都是来自输入的键值对;
  2)每个property名称都是键的名称;
  3)每个property都有get和set函数;
  4)我们需要在set函数里面发出修改前事件(prevSet)和修改完成事件(postSet)。
最后一条又是我加的,因为我需要在属性值被修改前对新值进行检验,修改
后提示有关界面(也为实现诸如MVP之类的模式提供支持)。
  这里的代码进行了简化,我们只处理属性值是字符串的情况。也没有(UITypeEditor)。
这样做是为了不分散大家注意力。

  我们已经知道要创建一个什么样的动态类型了,现在来看代码:

(这段代码来自项目exam2/utilities.cs)
Code
using System.Collections.Generic;
using System;
using System.Reflection.Emit;
using System.Reflection;

namespace demo2
{
   
///
<summary>
/// 描述一个属性容器,属性对象会实现这个接口
   
///
</summary>

public
interface IPropertyContainer
    {
        
///
<summary>
/// 获得属性名称列表
        
///
</summary>
        IEnumerable<string> PropertyNames { get; }

        
///
<summary>
/// 设置或获得属性值
        
///
</summary>
///
<param name="key">属性名</param>
///
<returns></returns>

object
this[string key] { get; set; }
    }

   
///
<summary>
/// 工具类
   
///
</summary>

public
static
class ObjectWrappeerBuilder
    {
        
///
<summary>
/// 设置动作执行前调用
        
///
</summary>
///
<param name="name">属性名</param>
///
<param name="value">属性值</param>
///
<returns>返回true才执行设置,否则不执行</returns>

public
delegate
bool PrevSet(string name, object value);

        
///
<summary>
/// 设置动作成功后代用
        
///
</summary>
///
<param name="name">属性名</param>
///
<param name="value">属性值</param>

public
delegate
void PostSet(string name, object value);

        
///
<summary>
/// 创建动态对象
        
///
</summary>
///
<param name="propObj">源对象</param>
///
<param name="prevSet">设置前置动作</param>
///
<param name="postSet">设置后置动作</param>
///
<returns>返回动态对象</returns>

public
static
object Build(IPropertyContainer propObj, PrevSet prevSet, PostSet postSet)
        {
            
//创建一个动态程序集“DynamicAssembly”
            AssemblyBuilder assemblyBuilder =
                AppDomain.CurrentDomain.DefineDynamicAssembly(
               
new AssemblyName("DynamicAssembly")
                , AssemblyBuilderAccess.Run);

            
//为程序集增加一个模块“DynamicModule”
            ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("DynamicModule");

            
//创建一个动态类型“”
            TypeBuilder typeBuilder = moduleBuilder.DefineType(propObj.GetType().Name +
"PropertiesWrapper"
                , TypeAttributes.NotPublic
| TypeAttributes.Class //这里设置类型的属性:非public 的class类型
                , typeof(object) //这里设置基本类为object,因为需要调用基类构造函数
                , null);

            
//为类型增加一个成员变量“element”,IPropertyContainer
            FieldBuilder eleFB = typeBuilder.DefineField("element", propObj.GetType()
                , System.Reflection.FieldAttributes.Private);

            
//为类型增加一个成员变量“prevSet”,PrevSet
            FieldBuilder prevSetFB = typeBuilder.DefineField("prevSet", prevSet.GetType()
                , System.Reflection.FieldAttributes.Private);

            
//为类型增加一个成员变量“postSet”,PostSet
            FieldBuilder postSetFB = typeBuilder.DefineField("postSet", postSet.GetType()
                , System.Reflection.FieldAttributes.Private);

            
//将键值对添加成类型的property

foreach (string key in propObj.PropertyNames)
            {
                BuildProperty(propObj, typeBuilder, prevSet, prevSetFB, postSet
                    , postSetFB, eleFB, key, propObj[key]);
            }

            
//获得类型数组,一般在各种反射函数中
            Type[] argTypes =
new Type[] { propObj.GetType(), prevSet.GetType(), postSet.GetType() };

            
//定义构造函数,我们要用这种方式将外部值传给动态类型
            ConstructorBuilder defCtorBuilder
               
= typeBuilder.DefineConstructor(MethodAttributes.Public
                , CallingConventions.Standard
                , argTypes
//我们刚定义的类型数组在这里描述了构造函数的参数列表
                );

            
//获得构造函数的ILGenerator
            ILGenerator cilg = defCtorBuilder.GetILGenerator();

            
//获得基类构造函数
            ConstructorInfo objCtor = Type.GetType("System.Object").GetConstructor(new Type[0]);

            
//下面的IL代码可以翻译成:

/*
             * ctor(IPropertyContainer propObj, PrevSet prevSet, PostSet postSet)
             * :base()
             * {
             *  this.element = propObj;
             *  this.prevSet = prevSet;
             *  this.postSet = postSet;
             * }
             *
*/
            cilg.Emit(OpCodes.Ldarg_0);
            cilg.Emit(OpCodes.Call, objCtor);
            cilg.Emit(OpCodes.Ldarg_0);
            cilg.Emit(OpCodes.Ldarg_1);
            cilg.Emit(OpCodes.Stfld, eleFB);
            cilg.Emit(OpCodes.Ldarg_0);
            cilg.Emit(OpCodes.Ldarg_2);
            cilg.Emit(OpCodes.Stfld, prevSetFB);
            cilg.Emit(OpCodes.Ldarg_0);
            cilg.Emit(OpCodes.Ldarg_3);
            cilg.Emit(OpCodes.Stfld, postSetFB);
            cilg.Emit(OpCodes.Ret);

            
//创建类型
            Type type = typeBuilder.CreateType();

            
//创建实例

return Activator.CreateInstance(type, new
object[] { propObj, prevSet, postSet }, null);
        }

        
///
<summary>
/// 这是一个辅助函数,将键值对变成一个property
        
///
</summary>
///
<param name="propObj">源对象</param>
///
<param name="typeBuilder">类型描述</param>
///
<param name="prevSet">调用前事件代理</param>
///
<param name="prevSetFB">调用前事件成员变量描述</param>
///
<param name="postSet">调用后事件代理</param>
///
<param name="postSetFB">调用后事件成员变量描述</param>
///
<param name="eleFB">源对象成员变量描述</param>
///
<param name="key"></param>
///
<param name="val"></param>

private
static
void BuildProperty(IPropertyContainer propObj, TypeBuilder typeBuilder
            , PrevSet prevSet, FieldBuilder prevSetFB, PostSet postSet, FieldBuilder postSetFB
            , FieldBuilder eleFB,
string key, object val)
        {
            
//定义一个property
            PropertyBuilder propBuilder = typeBuilder.DefineProperty(key
                , System.Reflection.PropertyAttributes.None, val.GetType(),
null);

            
//定义一个函数用作property的get函数
            MethodBuilder getMethodBuilder = typeBuilder.DefineMethod("get_"
+ key,
                MethodAttributes.Public,
                val.GetType(),
new Type[] { });

            
//获得该函数ILGenerator
            ILGenerator ilg = getMethodBuilder.GetILGenerator();

            
//获得IPropertyContainer的property: this[string key],的描述
            PropertyInfo pi =
typeof(IPropertyContainer).GetProperty("Item", new Type[] { typeof(string) });

            
//下面的IL代码可以翻译成:

/*
             * get
             * {
             *  return element[key];
             * }
             *
*/
            ilg.Emit(OpCodes.Ldarg_0);
            ilg.Emit(OpCodes.Ldfld, eleFB);
            ilg.Emit(OpCodes.Ldstr, key);
            ilg.EmitCall(OpCodes.Call, pi.GetGetMethod(),
null);
            ilg.Emit(OpCodes.Ret);

            
//将该函数设置为property的get函数
            propBuilder.SetGetMethod(getMethodBuilder);

            
//定义一个函数用作property的set函数
            MethodBuilder setMethodBuilder = typeBuilder.DefineMethod("set_"
+ key,
                MethodAttributes.Public,
               
null, new Type[] { val.GetType() });

            
//获得该函数ILGenerator
            ilg = setMethodBuilder.GetILGenerator();

            
//下面的IL代码可以翻译成:

/*
             * set
             * {
             *  if(this.prevSet(value))
             *  {
             *      this.element[key] = value;
             *      this.postSet(value);
             *  }
             * }
             *
             * 特别说明一下,大家会发现经常出现一个ilg.Emit(OpCodes.Ldarg_0);的调用
             * 其实这是在将this指针放到堆栈顶端,因为非静态的成员函数的第一个参数其实
             * 就是this指针,因此只要你需要引用成员都需要先调用这段代码。如果有Python
             * 的开发经验对这个就不难理解了。
            
*/
            System.Reflection.Emit.Label get_out
= ilg.DefineLabel();

            
////调用前置函数,判断是否允许设置
            ilg.Emit(OpCodes.Ldarg_0);
            ilg.Emit(OpCodes.Ldfld, prevSetFB);
            ilg.Emit(OpCodes.Ldstr, key);
            ilg.Emit(OpCodes.Ldarg_1);
            ilg.Emit(OpCodes.Call, prevSet.GetType().GetMethod(
"Invoke"));
            ilg.Emit(OpCodes.Brfalse, get_out);
//如果为false跳转至get_out

            
//调用设置函数
            ilg.Emit(OpCodes.Ldarg_0);
            ilg.Emit(OpCodes.Ldfld, eleFB);
            ilg.Emit(OpCodes.Ldstr, key);
            ilg.Emit(OpCodes.Ldarg_1);
            ilg.EmitCall(OpCodes.Callvirt, pi.GetSetMethod(),
null);

            
//调用后置函数
            ilg.Emit(OpCodes.Ldarg_0);
            ilg.Emit(OpCodes.Ldfld, postSetFB);
            ilg.Emit(OpCodes.Ldstr, key);
            ilg.Emit(OpCodes.Ldarg_1);
            ilg.Emit(OpCodes.Call, postSet.GetType().GetMethod(
"Invoke"));
            ilg.MarkLabel(get_out);
            ilg.Emit(OpCodes.Ret);
            
            
//将该函数设置为property的set函数
            propBuilder.SetSetMethod(setMethodBuilder);
        }
    }
}

举报本楼

您需要登录后才可以回帖 登录 | 注册 |

手机版|C114 ( 沪ICP备12002291号-1 )|联系我们 |网站地图  

GMT+8, 2024-11-16 18:48 , Processed in 0.650328 second(s), 15 queries , Gzip On.

Copyright © 1999-2023 C114 All Rights Reserved

Discuz Licensed

回顶部