反射的Emit实现
反射的 Emit实现
在日常开发中,我们频繁用到反射,除了最“繁重”的原始反射方式(这里不讲解),以及 4.0的关键字dynamic,还有许多的方式(Emit以及Linq)。接下来的演示,我将着重对以下这个类进行操作。
public class User
{
public string Username { get; set; }
}
{
public string Username { get; set; }
}
这是一个普通到不能再普通的类。我们要做的工作就是将Username属性进行赋值。在以往的反射,这个性能恐怕令人难以接受。
首先说第一种,不说,直接贴代码,代码中有详细注释。
代码
delegate void SetValueDelegate(string u);
static void Work3()
{
User u = new User();
string methodName = "set_Username"; // 获取函数(命名约束)
var method = u.GetType().GetMethod(methodName); // 查找函数
var type = method.GetParameters()[0].ParameterType; // 返回第一个参数的数据类型
var parameter = Expression.Parameter(type, "value"); // 创建一个表达式参数
// 创建指定 实例、函数、参数 的调用函数
var callExpression = Expression.Call(Expression.Constant(u), method, parameter);
// 创建调用函数的表达式
Expression<SetValueDelegate> lambdaExpression = Expression.Lambda<SetValueDelegate>(callExpression, parameter);
SetValueDelegate dd = lambdaExpression.Compile();// 生成可执行函数的委托。
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < Count; i++)
{
dd("Hello2");
}
watch.Stop();
Console.WriteLine("Work3 Linq Expression:\t{0}", watch.Elapsed.ToString());
}
static void Work3()
{
User u = new User();
string methodName = "set_Username"; // 获取函数(命名约束)
var method = u.GetType().GetMethod(methodName); // 查找函数
var type = method.GetParameters()[0].ParameterType; // 返回第一个参数的数据类型
var parameter = Expression.Parameter(type, "value"); // 创建一个表达式参数
// 创建指定 实例、函数、参数 的调用函数
var callExpression = Expression.Call(Expression.Constant(u), method, parameter);
// 创建调用函数的表达式
Expression<SetValueDelegate> lambdaExpression = Expression.Lambda<SetValueDelegate>(callExpression, parameter);
SetValueDelegate dd = lambdaExpression.Compile();// 生成可执行函数的委托。
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < Count; i++)
{
dd("Hello2");
}
watch.Stop();
Console.WriteLine("Work3 Linq Expression:\t{0}", watch.Elapsed.ToString());
}
Linq反射的性能十分可观,一般来说只有直接调用的3-6倍的损失(不包括创建委托的那些)。可是这样有一个缺点,就是说,有些时候我们并不知道“属性”的数据类型是什么,这种情况下的Linq反射就显得有点鸡肋。
Emit,反射中性能损失最小,基本保持在2-4倍的损失。其实Linq反射的内部也是调用Emit。
代码
public class FastProperty<T>
{
public delegate void SetValueDelegateHandler(T owner, object value);
private readonly Type ParameterType = typeof(object);
private T _owner;
public T Owner { get { return this._owner; } }
private Type _ownerType;
public FastProperty(T owner)
{
this._owner = owner;
this._ownerType = typeof(T);
}
public SetValueDelegateHandler SetPropertyValue(string propertyName, object value)
{
// 指定函数名
string methodName = "set_" + propertyName;
// 搜索函数,不区分大小写 IgnoreCase
var callMethod = this._ownerType.GetMethod(methodName, BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.NonPublic);
// 获取参数
var para = callMethod.GetParameters()[0];
// 创建动态函数
DynamicMethod method = new DynamicMethod("EmitCallable", null, new Type[] { this._ownerType, ParameterType }, this._ownerType.Module);
// 获取动态函数的 IL 生成器
var il = method.GetILGenerator();
// 创建一个本地变量,主要用于 Object Type to Propety Type
var local = il.DeclareLocal(para.ParameterType, true);
// 加载第 2 个参数【(T owner, object value)】的 value
il.Emit(OpCodes.Ldarg_1);
if (para.ParameterType.IsValueType)
{
il.Emit(OpCodes.Unbox_Any, para.ParameterType);// 如果是值类型,拆箱 string = (string)object;
}
else
{
il.Emit(OpCodes.Castclass, para.ParameterType);// 如果是引用类型,转换 Class = object as Class
}
il.Emit(OpCodes.Stloc, local);// 将上面的拆箱或转换,赋值到本地变量,现在这个本地变量是一个与目标函数相同数据类型的字段了。
il.Emit(OpCodes.Ldarg_0); // 加载第一个参数 owner
il.Emit(OpCodes.Ldloc, local);// 加载本地参数
il.EmitCall(OpCodes.Callvirt, callMethod, null);//调用函数
il.Emit(OpCodes.Ret); // 返回
/* 生成的动态函数类似:
* void EmitCallable(T owner, object value)
* {
* T local = (T)value;
* owner.Method(local);
* }
*/
return method.CreateDelegate(typeof(SetValueDelegateHandler)) as SetValueDelegateHandler;
}
}
{
public delegate void SetValueDelegateHandler(T owner, object value);
private readonly Type ParameterType = typeof(object);
private T _owner;
public T Owner { get { return this._owner; } }
private Type _ownerType;
public FastProperty(T owner)
{
this._owner = owner;
this._ownerType = typeof(T);
}
public SetValueDelegateHandler SetPropertyValue(string propertyName, object value)
{
// 指定函数名
string methodName = "set_" + propertyName;
// 搜索函数,不区分大小写 IgnoreCase
var callMethod = this._ownerType.GetMethod(methodName, BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.NonPublic);
// 获取参数
var para = callMethod.GetParameters()[0];
// 创建动态函数
DynamicMethod method = new DynamicMethod("EmitCallable", null, new Type[] { this._ownerType, ParameterType }, this._ownerType.Module);
// 获取动态函数的 IL 生成器
var il = method.GetILGenerator();
// 创建一个本地变量,主要用于 Object Type to Propety Type
var local = il.DeclareLocal(para.ParameterType, true);
// 加载第 2 个参数【(T owner, object value)】的 value
il.Emit(OpCodes.Ldarg_1);
if (para.ParameterType.IsValueType)
{
il.Emit(OpCodes.Unbox_Any, para.ParameterType);// 如果是值类型,拆箱 string = (string)object;
}
else
{
il.Emit(OpCodes.Castclass, para.ParameterType);// 如果是引用类型,转换 Class = object as Class
}
il.Emit(OpCodes.Stloc, local);// 将上面的拆箱或转换,赋值到本地变量,现在这个本地变量是一个与目标函数相同数据类型的字段了。
il.Emit(OpCodes.Ldarg_0); // 加载第一个参数 owner
il.Emit(OpCodes.Ldloc, local);// 加载本地参数
il.EmitCall(OpCodes.Callvirt, callMethod, null);//调用函数
il.Emit(OpCodes.Ret); // 返回
/* 生成的动态函数类似:
* void EmitCallable(T owner, object value)
* {
* T local = (T)value;
* owner.Method(local);
* }
*/
return method.CreateDelegate(typeof(SetValueDelegateHandler)) as SetValueDelegateHandler;
}
}
我测试了一下,不知道怎么回事,明明在动态函数里执行了“拆箱”,速度却比指定数据类型,还要快。奇怪……
上面只是演示了一个动态属性赋值(其实是调用函数)的示例,当然你更可以来一个命名约束:
private string _name;
public string Name { get { return this._name;
} set { this._name = value; } }
然后对字段 _name进行复制。
最后唠叨一句,如果您觉得我的文章有纯在“误解他人”的地方,欢迎指出,只有批评,才有成长。