作者:梁利锋 标签:DotNet, DbEntry

4473天前 (阅读:4456)
  偶然的机会,见到另一个商业 .Net ORM 组件 DataObjects.NET,它的定义数据对象的方式很特别:使用 abstract 的属性。简单的思考就可以知道,它使用的是动态创建类的方式,而且,因为只定义了纯虚的属性,所以,书写上面也非常的简单,简单到和基本的字段定义语法不相上下了:
public abstract class AbstractClass
{
    public abstract string Name { get; set; }
}

  这种思路真的不错,而且,对于类似 Linq 那种要在属性的 set 方法中做更多手脚的情况,也很适用,可以使得简单而重复的代码不再需要程序员,或者代码生成器生成,从而避免了简单而重复的代码的维护问题。

  至于那个 ORM 组件,我简单看了一下,除了这一个动态数据类的生成之外,使用上并没有 DbEntry.Net 方便 :),所以对于它细节的使用方法也就不研究了。那么,如果要创建自己的动态数据类,该怎么做呢?下载了 Castle Project 的源代码,把其中的 DynamicProxy 单独拿出来看了看,嗯嗯,ILGenerator 什么的,使用方法倒是很简单,而且对我感到不容易处理的序列化问题,它也处理得很好。只是很奇怪,DynamicProxy 的签名文件并没有包含在源代码中,而它的单元测试也不知道什么原因,有一半左右都通不过,另外,如果只使用 ProxyGenerator 的话,好像也不支持这种纯虚属性,如果使用 EasyType 之类的创建方法,似乎也不比直接使用 .Net Framework 简单多少了吧。

  好吧,Emit 就 Emit,动态生成类毕竟也是很有趣的技术,自己写写看吧。测试驱动开发,那么,它该怎么使用呢?
[Test]
public void Test1()
{
    DynamicObjectHelper h = new DynamicObjectHelper();
    AbstractClass c = h.NewObject<AbstractClass>();
    Assert.IsNotNull(c);
    c.Name = "Tom";
    Assert.AreEqual("Tom", c.Name);
}

  嗯,就差实现了,具体实现过程中反反复复查资料、做实验的细节就不说了,不过,应该说,写这种动态类的程序,Reflector 确实是一个好工具 :)
public class DynamicObjectHelper
{
    private const MethodAttributes OverrideFlag = MethodAttributes.Public |
        MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.Virtual;

    public T NewObject<T>()
    {
        AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly(
            new AssemblyName("DynamicObjectGen"), AssemblyBuilderAccess.Run);
        ModuleBuilder mb = ab.DefineDynamicModule("DynamicObjectGen");
        TypeBuilder tb = mb.DefineType("Test",
            TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.Serializable, typeof(T));
        tb.DefineDefaultConstructor(MethodAttributes.Public);

        FieldInfo fi = tb.DefineField("m_Name_1", typeof(string), FieldAttributes.Family);

        MethodBuilder b1 = tb.DefineMethod("get_Name", OverrideFlag, typeof(string), null);
        ILGenerator g1 = b1.GetILGenerator();
        g1.Emit(OpCodes.Ldarg_0);
        g1.Emit(OpCodes.Ldfld, fi);
        g1.Emit(OpCodes.Ret);
        tb.DefineMethodOverride(b1, typeof(T).GetMethod("get_Name"));

        MethodBuilder b2 = tb.DefineMethod("set_Name", OverrideFlag, null, new Type[] { typeof(string) });
        ILGenerator g2 = b2.GetILGenerator();
        g2.Emit(OpCodes.Ldarg_0);
        g2.Emit(OpCodes.Ldarg_1);
        g2.Emit(OpCodes.Stfld, fi);
        g2.Emit(OpCodes.Ret);
        tb.DefineMethodOverride(b2, typeof(T).GetMethod("set_Name"));

        Type t = tb.CreateType();
        return (T)Activator.CreateInstance(t);
    }
}

  运行单元测试 Test1,通过。

  好吧,这里实现的其实是针对 AbstractClass 特化的 NewObject 函数 —— 实现仅应刚刚使单元测试通过,呵呵。

  第一步的实现大概就是这样,花了差不多一下午的时间。除了非特化,其实还有很多细节问题需要处理,慢慢来。

lingate(非注册用户) 2006-11-24 13:46:57

在IDE下智能提示还正常么?

梁利锋 2006-11-24 15:22:31

在IDE下智能提示很正常,正常的不能再正常了 :)

Perfcet answer! That really gets to the heart of i(非注册用户) 2014-3-27 10:37:13

Perfcet answer! That really gets to the heart of it!