DynamicMethod:更接近IL

周末尝试着学习emit进行高级编程。想起若干天前收藏的 Herbrandson 在codeproject上的 神作 (此文技术含量其实一般,实际上他讲的就是用 DynamicMethod ILGenerator 实现快速实体创建的方法,但是文笔睿智,用词精致,非常诱人),重温了一下原文,又参考了msdn和园子里几位高手的博客,整理一下自己的学习笔记,希望对您有用。

一、常见的将DataReader转换为List的方法
1、类似codesmith的模板生成方式
熟悉代码生成器的童鞋一定知道的。在实体类里生成了“一堆”字段,属性,数据类型等等相关的判断(不是switch...case就是if...else等等等等)。其实也就类似Herbranson原文里介绍的第一种实现方式。毫无疑问,这种方式简单直接明了,是个程序员都看得懂,但是多数人会嫌弃代码啰嗦,生成的类会显得非常臃肿庞大。
public   class  ManualBuilder
{
    
public  Person Build(SqlDataReader reader)
    {
        Person person 
=   new  Person();

        
if  ( ! reader.IsDBNull( 0 ))
        {
            person.ID 
=  (Guid)reader[ 0 ];
        }

        
if  ( ! reader.IsDBNull( 1 ))
        {
            person.Name 
=  ( string )reader[ 1 ];
        }

        
if  ( ! reader.IsDBNull( 2 ))
        {
            person.Kids 
=  ( int )reader[ 2 ];
        }

        
if  ( ! reader.IsDBNull( 3 ))
        {
            person.Active 
=  ( bool )reader[ 3 ];
        }

        
if  ( ! reader.IsDBNull( 4 ))
        {
            person.DateOfBirth 
=  (DateTime)reader[ 4 ];
        }

        
return  person;
    }
}

2、反射,反射,兴高采烈地登场
大家都觉得上面第一种的实现代码毫无美感,通过反射,世界就清静多了:

public   class  ReflectionBuilder < />
{
    
private  PropertyInfo[] properties;

    
private  ReflectionBuilder() { }

    
public  T Build(SqlDataReader reader)
    {
        T result 
=  (T)Activator.CreateInstance( typeof (T));

        
for  ( int  i  =   0 ; i  <  reader.FieldCount; i ++ )
        {
            
if  (properties[i]  !=   null   &&   ! reader.IsDBNull(i))
            {
                properties[i].SetValue(result, reader[i], 
null );
            }
        }

        
return  result;
    }

    
public   static  ReflectionBuilder < t >  CreateBuilder(SqlDataReader reader)
    {
        ReflectionBuilder
< t >  result  =   new  ReflectionBuilder < t > ();

        result.properties 
=   new  PropertyInfo[reader.FieldCount];
        
for  ( int  i  =   0 ; i  <  reader.FieldCount; i ++ )
        {
            result.properties[i] 
=   typeof (T).GetProperty(reader.GetName(i));
        }

        
return  result;
    }
}

观察上面的代码,大家会发现程序的通用性大大增强,减少了大量重复的工作,技术含量也是杠杠的。
ps:前面两种方式的实现代码都是原文里拷贝来的。大家最常见的就是这两种方式,所以懒得写注释了,没什么难度的。
ps1:通过反射转换实体,其实还有其他写法的,但是主要思想大同小异(厚颜推荐楼猪的关于ado.net的旧文)。

二、来一点点emit,性能提高不是一点点
这就是传说已久的通过emit创建动态代理,实现实体的创建:

//  ========================================================================================
//  Information: provide by Herbrandson
//  Source:  http://www.codeproject.com/KB/database/DynamicMethod_ILGenerator.aspx
//  ========================================================================================
using  System;
using  System.Data;
using  System.Reflection;
using  System.Reflection.Emit;

///  
/// Use DynamicMethod and ILGenerator create entity
///  

///  
public   class  DynamicBuilder < T >
{
    
private   static   readonly  MethodInfo getValueMethod  =   typeof (IDataRecord).GetMethod( " get_Item " new  Type[] {  typeof ( int ) });

    
private   static   readonly  MethodInfo isDBNullMethod  =   typeof (IDataRecord).GetMethod( " IsDBNull " new  Type[] {  typeof ( int ) });

    
private   delegate  T Load(IDataRecord dataRecord);

    
private  Load handler; // 最终执行动态方法的一个委托 参数是IDataRecord接口

    
private  DynamicBuilder() { } // 私有构造函数

    
public  T Build(IDataRecord dataRecord)
    {
        
return  handler(dataRecord); // 执行CreateBuilder里创建的DynamicCreate动态方法的委托
    }

    
public   static  DynamicBuilder < T >  CreateBuilder(IDataRecord dataRecord)
    {
        DynamicBuilder
< T >  dynamicBuilder  =   new  DynamicBuilder < T > ();

        
// 定义一个名为DynamicCreate的动态方法,返回值typof(T),参数typeof(IDataRecord)
        DynamicMethod method  =   new  DynamicMethod( " DynamicCreate " typeof (T),  new  Type[] {  typeof (IDataRecord) },  typeof (T),  true );

        ILGenerator generator 
=  method.GetILGenerator(); // 创建一个MSIL生成器,为动态方法生成代码

        LocalBuilder result 
=  generator.DeclareLocal( typeof (T)); // 声明指定类型的局部变量 可以T t;这么理解

        
// The next piece of code instantiates the requested type of object and stores it in the local variable. 可以t=new T();这么理解
        generator.Emit(OpCodes.Newobj,  typeof (T).GetConstructor(Type.EmptyTypes));
        generator.Emit(OpCodes.Stloc, result);

        
for  ( int  i  =   0 ; i  <  dataRecord.FieldCount; i ++ ) // 数据集合,熟悉的for循环 要干什么你懂的 
        {
            PropertyInfo propertyInfo 
=   typeof (T).GetProperty(dataRecord.GetName(i)); // 根据列名取属性  原则上属性和列是一一对应的关系
            Label endIfLabel  =  generator.DefineLabel();

            
if  (propertyInfo  !=   null   &&  propertyInfo.GetSetMethod()  !=   null ) // 实体存在该属性 且该属性有SetMethod方法
            {
                
/* The code then loops through the fields in the data reader, finding matching properties on the type passed in. 
                 * When a match is found, the code checks to see if the value from the data reader is null.
                 
*/
                generator.Emit(OpCodes.Ldarg_0);
                generator.Emit(OpCodes.Ldc_I4, i);
                generator.Emit(OpCodes.Callvirt, isDBNullMethod);
// 就知道这里要调用IsDBNull方法 如果IsDBNull==true contine
                generator.Emit(OpCodes.Brtrue, endIfLabel);

                
/* If the value in the data reader is not null, the code sets the value on the object. */
                generator.Emit(OpCodes.Ldloc, result);
                generator.Emit(OpCodes.Ldarg_0);
                generator.Emit(OpCodes.Ldc_I4, i);
                generator.Emit(OpCodes.Callvirt, getValueMethod);
// 调用get_Item方法
                generator.Emit(OpCodes.Unbox_Any, dataRecord.GetFieldType(i));
                generator.Emit(OpCodes.Callvirt, propertyInfo.GetSetMethod());
// 给该属性设置对应值

                generator.MarkLabel(endIfLabel);
            }
        }

        
/* The last part of the code returns the value of the local variable */
        generator.Emit(OpCodes.Ldloc, result);
        generator.Emit(OpCodes.Ret);
// 方法结束,返回

        
// 完成动态方法的创建,并且创建执行该动态方法的委托,赋值到全局变量handler,handler在Build方法里Invoke
        dynamicBuilder.handler  =  (Load)method.CreateDelegate( typeof (Load));
        
return  dynamicBuilder;
    }
}

(1)、因为老外的这个实现方式涉及到emit的知识,大部分开发者可能了解的并不是很深入,楼猪也不是很熟悉,而且楼猪极不习惯emit这种编程风格,感觉它非常破坏代码的美感,这种牺牲色相的高级写法如果在我们的项目中大面积出现,估计大部分童鞋都会抓狂的。好在现在终于有了工具可以“Emit with a human face”了。
(2)、不管怎样,还是凭借自己学习到的一点知识储备,大胆按照自己的理解,写了点注释,对照原文作者的意思,不见得很烂,就是这么自信,呵呵。
在外部调用的方式如下(测试代码,无比丑陋,请留意):

   static   void  Main( string [] args)
        {
            
string  strConn  =   @" Data Source=.\sqlexpress;Initial Catalog=TestDb;Persist Security Info=True;User ID=sa;Password=123456 " ;
            
using  (SqlConnection conn  =   new  SqlConnection(strConn))
            {
                conn.Open();
                
string  sql  =   string .Format( " SELECT TOP 10 [Id],[FirstName] ,[LastName] ,[Weight] ,[Height] FROM Person(NOLOCK) " );
                
// string sql = string.Format("SELECT TOP 10 [FirstName] ,[LastName] ,[Height] FROM Person(NOLOCK)");

                SqlCommand cmd 
=  conn.CreateCommand();
                cmd.CommandText 
=  sql;
                SqlDataReader rdr 
=  cmd.ExecuteReader();
                IList
< Person >  listPersons  =   new  List < Person > ();
                
while  (rdr.Read())
                {
                    Person model 
=  DynamicBuilder < Person > .CreateBuilder().Build(rdr);
                    listPersons.Add(model);
                }
                Console.WriteLine(
string .Format( " Theres are {0} people. " , listPersons.Count));
            }
            Console.Read();
        }

需要说明的是,生成实体的时候,while(rdr.Read())必须判断,原文里没有提及。

三、改进ado.net快速上手里的DataReader转换成实体对象
楼猪在旧文里曾经写了个数据转换类(ModelConverter),现在放弃反射的方式,采用Herbrandson介绍的动态代理实现数据实体的创建:

using  System;
using  System.Collections;
using  System.Collections.Generic;
using  System.Data;
using  System.Data.Common;
using  System.Reflection;
using  System.Threading;

namespace  AdoNetDataAccess.Core.Obj2Model
{
    
using  AdoNetDataAccess.Core.Contract;

    
public   sealed   class  ModelConverter
    {
        
private   static   readonly   object  objSync  =   new   object ();

        
#region  query for list

        
///  
        
///  查询数据表项并转换为对应实体
        
///  

        
///  
        
///  
        
///  
        
///  
         public   static  IList < T >  QueryForList < T > ( string  sqlStr, CommandType cmdType, List < DbParameter >  listParams, Type objType, IDbOperation dbOperation)
            
where  T :  class new ()
        {
            IDataReader rdr 
=  dbOperation.ExecuteReader(sqlStr, cmdType, listParams);
            IList
< T >  listModels  =   new  List < T > ();
            
try
            {
                Monitor.Enter(objSync);
                
while  (rdr.Read())
                {
                    T model 
=   default (T);
                    model 
=  DynamicBuilder < T > .CreateBuilder(rdr).Build(rdr); // 通过动态反射转换成实体
                    listModels.Add(model);
                }

                
/* 不用有人怨声载道的性能恶劣的反射了 */
                
// Hashtable ht = CreateHashColumnName(rdr);
                
// while (rdr.Read())
                
// {
                
//     Object obj = Activator.CreateInstance(objType);
                
//     PropertyInfo[] properties = objType.GetProperties();
                
//     foreach (PropertyInfo propInfo in properties)
                
//     {
                
//         string columnName = propInfo.Name.ToUpper();
                
//         if (ht.ContainsKey(columnName) == false)
                
//         {
                
//             continue;
                
//         }
                
//         int index = rdr.GetOrdinal(propInfo.Name);
                
//         object columnValue = rdr.GetValue(index);
                
//         if (columnValue != System.DBNull.Value)
                
//         {
                
//             SetValue(propInfo, obj, columnValue);
                
//         }
                
//     }
                
//     T model = default(T);
                
//     model = obj as T;
                
//     listModels.Add(model);
                
// }
            }
            
finally
            {
                rdr.Close();
                rdr.Dispose();
                Monitor.Exit(objSync);
            }
            
return  listModels;
        }

        
#endregion

        
#region  query for dictionary

        
///  
        
///  查询数据表项并转换为对应实体
        
///  

        
///  
        
///  
        
///   字典对应key列名
        
///  
        
///  
        
///  
         public   static  IDictionary < K, T >  QueryForDictionary < K, T > ( string  key,  string  sqlStr, CommandType cmdType, List < DbParameter >  listParams, Type objType, IDbOperation dbOperation)
            
where  T :  class new ()
        {
            IDataReader rdr 
=  dbOperation.ExecuteReader(sqlStr, cmdType, listParams);
            IDictionary
< K, T >  dictModels  =   new  Dictionary < K, T > ();
            
try
            {
                Monitor.Enter(objSync);
                
while  (rdr.Read())
                {
                    T model 
=   default (T);
                    model 
=  DynamicBuilder < T > .CreateBuilder(rdr).Build(rdr); // 通过动态反射转换成实体
                    
// K objKey = GetModelKey(key, model);
                    K objKey  =  BuildPrimaryKey < K, T > (key, rdr);
                    dictModels.Add(objKey, model);
                }

                
/* 不用有人怨声载道的性能恶劣的反射了 */
                
// Hashtable ht = CreateHashColumnName(rdr);
                
// while (rdr.Read())
                
// {
                
//     Object obj = Activator.CreateInstance(objType);
                
//     PropertyInfo[] properties = objType.GetProperties();
                
//     object dictKey = null;
                
//     foreach (PropertyInfo propInfo in properties)
                
//     {
                
//         string columnName = propInfo.Name.ToUpper();
                
//         if (ht.ContainsKey(columnName) == false)
                
//         {
                
//             continue;
                
//         }
                
//         int index = rdr.GetOrdinal(propInfo.Name);
                
//         object columnValue = rdr.GetValue(index);
                
//         if (columnValue != System.DBNull.Value)
                
//         {
                
//             SetValue(propInfo, obj, columnValue);
                
//             if (string.Compare(columnName, key.ToUpper()) == 0)
                
//             {
                
//                 dictKey = columnValue;
                
//             }
                
//         }
                
//     }
                
//     T model = default(T);
                
//     model = obj as T;
                
//     K objKey = (K)dictKey;
                
//     dictModels.Add(objKey, model);
                
// }
            }
            
finally
            {
                rdr.Close();
                rdr.Dispose();
                Monitor.Exit(objSync);
            }
            
return  dictModels;
        }

        
#endregion

        
#region  internal util

        
// private static Hashtable CreateHashColumnName(IDataReader rdr)
        
// {
        
//     int len = rdr.FieldCount;
        
//     Hashtable ht = new Hashtable(len);
        
//     for (int i = 0; i < len; i++)
        
//     {
        
//         string columnName = rdr.GetName(i).ToUpper();  // 不区分大小写
        
//         string columnRealName = rdr.GetName(i);
        
//         if (ht.ContainsKey(columnName) == false)
        
//         {
        
//             ht.Add(columnName, columnRealName);
        
//         }
        
//     }
        
//     return ht;
        
// }

        
// private static void SetValue(PropertyInfo propInfo, Object obj, object objValue)
        
// {
        
//     try
        
//     {
        
//         propInfo.SetValue(obj, objValue, null);
        
//     }
        
//     catch
        
//     {
        
//         object realValue = null;
        
//         try
        
//         {
        
//             realValue = Convert.ChangeType(objValue, propInfo.PropertyType);
        
//             propInfo.SetValue(obj, realValue, null);
        
//         }
        
//         catch (Exception ex)
        
//         {
        
//             string err = ex.Message;
        
//              // throw ex;  // 在数据库数据有不符合规范的情况下应该及时抛出异常
        
//         }
        
//     }
        
// }

        
// private static K GetModelKey(string key, T model)
        
// {
        
//     object dictKey = null;
        
//     PropertyInfo[] properties = model.GetType().GetProperties();
        
//     foreach (PropertyInfo propInfo in properties)
        
//     {
        
//         if (string.Compare(propInfo.Name.ToUpper(), key.ToUpper()) == 0)
        
//         {
        
//             dictKey = propInfo.GetValue(model, null);
        
//             break;
        
//         }
        
//     }
        
//     K objKey = (K)dictKey;
        
//     return objKey;
        
// }

        
private   static  K BuildPrimaryKey < K, T > ( string  key, IDataRecord dataRecord)
        {
            K dictKey 
=   default (K);
            
for  ( int  i  =   0 ; i  <  dataRecord.FieldCount; i ++ )
            {
                PropertyInfo propertyInfo 
=   typeof (T).GetProperty(dataRecord.GetName(i));
                
if  (propertyInfo  !=   null   &&   string .Compare(key.ToUpper(), propertyInfo.Name.ToUpper())  ==   0 )
                {
                    
object  value  =  dataRecord.GetValue(i);
                    dictKey 
=  (K)value;
                    
break ;
                }
            }
            
return  dictKey;
        }

        
#endregion
    }
}

DynamicBuilder类就是上面介绍的通过DynamicMethod和ILGenerator的方式。这样,QueryForList和QueryForDictionary方法的性能就得到了改善。
需要说明的是,在实体转换的时候,QueryForDictionary方法的字典的key的获取是通过BuildPrimaryKey泛型方法,直接从IDataReader里取值,而没有通过emit生成动态代理获取(有心的童鞋可以小试牛刀,看看如何通过构建动态代理获取key),个人认为使用emit适可而止,没必要矫枉过正。
  
先写到这里了。你还在纠结要不要学习emit? Yes,it works.

demo下载:demo

你可能感兴趣的:(CLR与.NET基类库)