题记:写这篇博客要主是加深自己对反射方法的认识和总结实现算法时的一些验经和训教,如果有错误请指出,万分感谢。
原文地址:http://www.codeproject.com/Articles/503527/Reflection-optimization-techniques
简介
反射是 Microsoft .NET framework 中非常强大的特性,它在 System.Reflection 命名空间中供给了一整套在运行时动态载入/处理程序集的 API。
反射 (Reflection) 如此强大,但仍需谨慎应用,因为它实际上是一种用性能换扩展性的一种方法,这里供给了一些优化反射的方法,以期把对性能的影响最小化。
场景1(动态调用一个方法)
一个标准且完整的动态调用方法示例
public void DynamicExecution()
{
Type objType = Type.GetType("DynamicExecution.Test.DynamicClass");
object obj = Activator.CreateInstance(objType);
MethodInfo mInfo = objType.GetMethod("AddNumbers",
new Type[] { typeof(int), typeof(int) });
// 动态调用 obj 对象上的 AddNumbers 方法
mInfo.Invoke(obj, new object[] { 1, 5 });
PropertyInfo pInfo = objType.GetProperty("TEST_ID");
// 动态设置 obj 对象的 ID 属性
pInfo.SetValue(obj, "TEST_ID", null );
}
缺点:
- 每次动态调用方法(或属性)将比静态调用方法慢好几倍
- 编译时方法/属性的传入参数的类型安全性没法检查,在运行时传入错误数据/类型就会挂掉
- 编码量大,且可读性不高
优化代码
Public void OptimizedDynamicExecution()
{
Type objType = Type.GetType("DynamicExecution.Test.DynamicClass");
IDynamicClass obj = Activator.CreateInstance(objType) as IDynamicClass;
if (null != obj)
{
// 静态调用 obj 对象上的 AddNumbers 方法
int result = obj.AddNumbers(1, 5);
// 静态设定 obj 对象的 ID 属性
obj.ID = 10;
}
}
// 在 OptimizedDynamicExecution API 中应用的 Interface
public interface IDynamicClass
{
int ID { get; set; }
int AddNumbers(int a, int b);
}
改进点
- 静态调用进步了执行速度
- 引入一个 interface 实现了类型安全
- 该 interface 能在其他场景应用
- 更短的代码,更好的可读性
场景2(读取自定义属性custom attributes)
标准代码示例
[Table(Name="Employees")]
public class Employee : Entity
{
[PrimaryKey]
public int Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public DateTime DOB { get; set; }
}
public class Entity
{
public Entity()
{
Type curType = this.GetType();
object[] tableAttributes = curType.GetCustomAttributes(typeof(TableAttribute), true);
if(null != tableAttributes && tableAttributes.Count() > 0)
{
// Retrieve the attribute information
}
// Use the attribute information here
}
}
以上定义了一个 Employee 实体类,对应数据库中的表明为 “Employees”,另外它的 Id 属性标记了一个 PrimaryKey 属性。
为了获取这些定义在 custom attributes 中的信息,在父类中应用了反射。
缺点
- 每次实例化都通过反射来获取 custom attributes 信息
- 通过反射来获取 custom attributes 信息是个“重活”
优化代码
// 追加一个 C# 结构来存储信息
public struct TableInfo
{
public string TableName;
public string PrimaryKey;
}
public class Entity
{
private static Dictionary<Type, TableInfo> tableInfoList = new Dictionary<Type, TableInfo>();
public Entity()
{
Type curType = this.GetType();
TableInfo curTableInfo;
if (!tableInfoList.TryGetValue(curType, out curTableInfo))
{
lock (this)
{
// 应用两重检查来下降 lock 消耗
if (!tableInfoList.TryGetValue(curType, out curTableInfo))
{
object[] tableAttributes = curType.GetCustomAttributes(typeof(TableAttribute), true);
if(null != tableAttributes && tableAttributes.Count() > 0)
{
// 缓存中没有时,新建一个 TableInfo
curTableInfo = new TableInfo();
curTableInfo.TableName = ((TableAttribute) tableAttributes[0]).Name;
// 其他处理
//把新建的 TableInfo 参加缓存
tableInfoList.Add(curType, curTableInfo);
}
}
}
}
// use curTableInfo here
}
}
这里没有对反射操作停止修改,而是通过对反射的信息停止缓存从而增加反射操作的次数来进步程序的执行效率
优点
- 每种类型仅在首次应用时需要反射
- 通过缓存最小化反射的应用次数
场景3(依赖注入 Dependency Injection)
如今的程序都强调低耦合,允许在运行时动态载入配置文件中指定的程序集/模块,这平日都应用到反射并因此下降了运行效率。
Microsoft .NET 轻量级代码生成可以处理“创立设计时未知类型对象”的问题,它在 System.Reflection.Emit 命名空间下供给了多个 API。
这个技巧能增加反射的过度应用,并且通过缓存方法代理的方法来进步后续调用的执行效率。
// 依赖的协议接口
public interface ISecurityProvider
{
bool ValidateUser(string userId, string password);
List<User> GetUsersList();
}
// 依赖的具体实现类
public class DefaultSecurityProvider : ISecurityProvider
{
public bool ValidateUser(string userId, string password)
{
...
}
public List<User> GetUsersIist()
{
...
}
}
// 动态实例化 DefaultSecuirtyProvider 的方法
private void CreateInstance()
{
Type classType = Type.GetType("DefaultSecurityProvider, AssemblyName");
// 应用反射动态创立一个实例对象
ISecurityProvider employeeInstance =
Activator.CreateInstance(classType) as ISecurityProvider;
}
缺点
- 每次都应用反射来创立实例对象
- 通过反射来获取 custom attributes 信息是个“重活”
优化代码
喜马拉雅直冲霄汉,可上面有攀爬者的旗帜;撒哈拉沙漠一望无垠,可里面有跋涉者的脚印;阿尔卑斯山壁立千仞,可其中有探险者的身影;雅鲁藏布江湍急浩荡,可其中有勇敢者的故事。
// 动态创立实例(不应用反射)
private void CreateInstance()
{
Type classType = Type.GetType("DefaultSecurityProvider, AssemblyName");
// 获取一个指定对象的实例化方法的代理 delegate
CreateInstanceDelegate createInstance = ObjectInstantiater(classType);
// 执行该代理来获取指定对象的一个实例
ISecurityProvider employeeInstance = createInstance() as ISecurityProvider;
}
如果你想晓得的更多一些,可以参考以下代码(截取自 System.Reflection.Emit 命名空间)
// 实例方法的代理声明
public delegate object CreateInstanceDelegate();
// 缓存实例方法代理的 Dictionary
private static Dictionary<Type, CreateInstanceDelegate> _createInstanceDelegateList =
new Dictionary<Type, CreateInstanceDelegate>();
// 动态获取实例化指定类型的方法
public static CreateInstanceDelegate ObjectInstantiater(Type objectType)
{
CreateInstanceDelegate createInstanceDelegate;
if (!_createInstanceDelegateList.TryGetValue(objectType,
out createInstanceDelegate))
{
lock (objectType)
{
if (!_createInstanceDelegateList.TryGetValue(objectType, createInstanceDelegate))
{
// 建立一个新方法
DynamicMethod dynamicMethod =
new DynamicMethod("Create_" + objectType.Name, objectType, new Type[0]);
// 获取默认的构造函数
ConstructorInfo ctor = objectType.GetConstructor(new Type[0]);
// 生成 IL 代码
ILGenerator ilgen = dynamicMethod.GetILGenerator();
ilgen.Emit(OpCodes.Newobj, ctor);
ilgen.Emit(OpCodes.Ret);
// 建立一个代理并缓存在 dictionary 中
createInstanceDelegate = (CreateInstanceDelegate)dynamicMethod
.CreateDelegate(typeof(CreateInstanceDelegate));
_createInstanceDelegateList[objectType] = createInstanceDelegate;
}
}
}
return createInstanceDelegate; // 返回对象的实例化代理
}
优点
- 类对象都通过静态方法建立
- 这个技巧避免了用反射来停止类实例化
场景4(动态设定 ORM 实体对象的属性值)
绝大多数的 ORM Frameworks 中,实体类对应了数据库中的表,并通过反射来读取/设定实体类的各个属性,这非常影响性能。这可以鉴戒场景3的方法该改善,此处我们将应用.NET Frameworks 3.5 中包括的 Expression Trees 特性来实现。
标准代码
// Employees 实体类
public class Employees
{
public int EmployeeID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public DateTime BirthDate { get; set; }
}
// Employees 列表,用来存放从 data reader 读取到的全部数据
List<Employees> empList = new List<Employees>();
using(SqlConnection con = new SqlConnection(@"......"))
{
SqlCommand cmd = new SqlCommand("Select * from employees");
cmd.Connection = con;
con.Open();
// 调用 ReadList 把 data reader 查询出的信息存入实体类
using (SqlDataReader reader = cmd.ExecuteReader())
{
empList = ReadList<Employees>(reader);
}
}
// 把 data reader 查询出的信息转换成实体列表
public List<T> ReadList<T>(SqlDataReader reader) where T : new()
{
var list = new List<T>();
while (reader.Read())
{
T entity = new T();
Type entityType = typeof(T);
foreach(var entityProperty in entityType.GetProperties())
{
// 应用反射来设定实体对象的每一个属性
entityProperty.SetValue(entity, reader[entityProperty.Name], null);
} list.Add(entity);
}
return list;
}
缺点
- 通过反射来设定属性值是个“重活”
- 开发者需要自行确保每个属性的类型安全
优化代码
public List<T> ReadList<T>(SqlDataReader reader)
{
var list = new List<T>();
Func<SqlDataReader, T> readRow = GetReader<T>();
// 从 GetReader 函数获取一个代理,并通过该代理来创立对象
while (reader.Read())
{
list.Add(readRow(reader));
}
return list;
}
// 应用一个 ConcurrentDictionary 来缓存各类型的代理
ConcurrentDictionary<Type, Delegate> ExpressionCache = new ConcurrentDictionary<Type, Delegate>();
// 获取(并创立缓存)各类型的初始化 Expression Trees
// PS:原文未供给该函数,所以翻译的时候自己写了下(简略调试通过),若有问题请联系我
public Func<SqlDataReader, T> GetReader<T>()
{
Delegate func;
var entityType = typeof(T);
if (!ExpressionCache.TryGetValue(entityType, out func))
{
// lambda 输入参数
var readerParam = Expression.Parameter(typeof(SqlDataReader), "reader");
// lambda 处理
var assignmentExpressions = new List<MemberBinding>();
foreach (var entityProperty in entityType.GetProperties())
{
var assignmentExpression = Expression.Bind(
entityProperty.GetSetMethod(),
Expression.Convert(
Expression.Call(
readerParam,
typeof(SqlDataReader).GetMethod("get_Item", new[] { typeof(string) }),
Expression.Constant(entityProperty.Name, typeof(string))),
entityProperty.PropertyType));
assignmentExpressions.Add(assignmentExpression);
}
var bodyExporess = Expression.MemberInit(Expression.New(entityType), assignmentExpressions);
var lambda = Expression.Lambda<Func<SqlDataReader, T>>(bodyExporess, new[] { readerParam });
func = lambda.Compile();
ExpressionCache.TryAdd(entityType, func);
}
return (Func<SqlDataReader, T>)func;
}
优点
- 开发者不需要为实体类写赋值代码
- 建立的 expression tree 可被多个进程同享
- 建立 expression tree 相比写 IL 代码来说更简略
文章结束给大家分享下程序员的一些笑话语录: 警告
有一个小伙子在一个办公大楼的门口抽着烟,一个妇女路过他身边,并对他 说, “你知道不知道这个东西会危害你的健康?我是说, 你有没有注意到香烟 盒上的那个警告(Warning)?”
小伙子说,“没事儿,我是一个程序员”。
那妇女说,“这又怎样?”
程序员说,“我们从来不关心 Warning,只关心 Error”
--------------------------------- 原创文章 By
反射和方法
---------------------------------