博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
关于Expression Tree和IL Emit的所谓的"性能差别&quot“.NET研究”;
阅读量:6153 次
发布时间:2019-06-21

本文共 8663 字,大约阅读时间需要 28 分钟。

  昨天写了《》,有个网友写信问我一个问题:从性能上看,Expression Tree和IL Emit孰优孰劣?虽然我在回信中作了简单的回答,但不知道这个网友是否懂我的意思。反正今天呆在家里也没事儿,干脆再就这个话题再写一篇文章。

目录:

一、Expression Tree和IL Emit并不存在所谓的性能差异
二、属性赋值操作的两种写法
三、属性取值操作的两种写法
四、两种写法对应的IL

  一、Expression Tree和IL Emit并不存在所谓的性能差异

  Expression Tree和IL Emit的性能孰优孰劣,这本是个“不是问题的问题”。因为两者之间并不存在本质的区别,所以也谈不上性能的优劣问题。举个例子来说,我们知道.NET Framework 2.0,3.0和3.5使用的是相同的CLR。但是C# 3.0、3.5在2.0的基础上推出了很多语言层面的特性,比如自动实现属性:

 
public
class
Foo
{
public
Bar Bar{
get
;
set
;}
public
Foo()
{
this
.Bar
=
new
Bar();
}
}

  我们也可以按照下面“传统”的方式来写上面这段代码,谁都知道这两种写法在本质上是完全一样的。就上面的程序来说,在编译的时候C#编译器会将其转化成下一种形式,什么自动实现属性、匿名属性、扩展方法,都是浮云——语法糖而已。

 
public
class
Foo
{
private
Bar _bar;
public
Bar Bar
{
get
{
return
_bar;}
set
{_bar
=
value;}
}
public
Foo()
{
_bar
=
new
Bar();
}
}

  Expression Tree和IL Emit之间的关系与这些“语法糖”类似。编译后的Expression Tree就是IL代码;而IL Emit让我们可以用高级语言的编程方式来控制中间语言(IL)程序。由于最终的东西都是一样的,谈不上谁比谁好的问题。编译Expression Tree实现了向IL的转换,如果你通过IL Emit写的IL能够比Expression Tree自动转换的好,那么你的程序性能就好,否则性能就差。但是我们不能说Expression Tree和IL Emit在性能上孰优孰劣。

  二、属性赋值操作的两种写法

  我们说明Expression Tree和IL Emit之间不存在性能的差异,我们不妨写个例子。简单起见,我们还是采用前面谈到过的属性赋值和取值的操作为例。假设有如下一个接口IFoo,包含一个类型和名称均为Bar的可读写的属性。

 
public
interface
IFoo
{
Bar{
get
;
set
;}
}
public
class
Bar{}

  现在我们通过Expression Tree和IL Emit两种方式编写一个静态方法对IFoo对象的Bar属性进行赋值。简单起见,我们甚至将静态方法的参数类型直接指定为IFoo和Bar,从而省去了类型转换操作。下面是通过Expression Tree进行属性赋值的方法:SetPropertyValueViaExpression。

 
public
static
void
SetPropertyValueViaExpression(IFoo foo, Bar bar)
{
var property
=
typeof
(IFoo).GetProperty(
"
Bar
"
);
var target
=
Expression.Parameter(
typeof
(IFoo));
var propertyValue
=
Expression.Parameter(
typeof
(Bar));
var setPropertyValue
=
Expression.Call(target, property.GetSetMethod(), propertyValue);
var setAction
=
Expression.Lambda
<
Action
<
IFoo, Bar
>>
(setPropertyValue, target, propertyValue).Compile();
setAction(foo, bar);
}

  而下面的SetPropertyValueViaEmit则通过IL Emit的方式完成了一样的工作:

 
public
static
void
SetPropertyValueViaEmit(IFoo foo, Bar bar)
{
var property
=
typeof
(IFoo).GetProperty(
"
Bar
"
);
DynamicMethod method
=
new
DynamicMethod(
"
SetValue
"
,
null
,
new
Type[] {
typeof
(IFoo),
typeof
(Bar) });
ILGenerator ilGenerator
=
method.GetILGenerator();
ilGenerator.Emit(OpCodes.Ldarg_0);
ilGenerator.Emit(OpCodes.Ldarg_1);
ilGenerator.EmitCall(OpCodes.Callvirt, property.GetSetMethod(),
null
);
ilGenerator.Emit(OpCodes.Ret);
method.DefineParameter(
1
, ParameterAttributes.In,
"
obj
"
);
method.DefineParameter(
2
, ParameterAttributes.In,
"
value
"
);
var setAction
=
(Action
<
IFoo, Bar
>
)method.CreateDelegate(
typeof
(Action
<
IFoo, Bar
>
));
setAction(foo, bar);
}

  三、属性取值操作的两种写法

  接下来,我们来编写用于进行属性取值操作的方法。下面的SetPropertyValueViaExpression方法是基于Expression Tree的。

 
public
static
Bar GetPropertyValueViaExpression(IFoo foo)
{
var property
=
typeof
(IFoo).GetProperty(
"
Bar
"
);
var target
=
Expression.Parameter(
typeof
(IFoo));
var getPropertyValue
=
Expression.Property(target, property);
var getFunc
=
Expression.Lambda
<
Func
<
IFoo, Bar
>>
(getPropertyValue, target).Compile();
return
getFunc(foo);
}

  下面则是基于IL Emit的版本:

 
public
static
Bar GetPropertyValueViaEmit(IFoo foo)
{
var property
=
typeof
(IFoo).GetProperty(
"
Bar
"
);
DynamicMethod method
=
new
DynamicMethod(
"
GetValue
"
,
typeof
(Bar),
new
Type[] {
typeof
(IFoo) });
ILGenerator ilGenerator
=
method.GetILGenerator();
ilGenerator.Emit(OpCodes.Ldarg_0);
ilGenerator.EmitCall(OpCodes.Callvirt, property.GetGetMethod(),
null
);
ilGenerator.Emit(OpCodes.Ret);
method.DefineParameter(
1
, ParameterAttributes.In,
"
target
"
);
var getFunc
=
(Func
<
IFoo, Bar
>
)method.CreateDelegate(
typeof
(Func
<
IFoo, Bar
>
));
return
getFunc(foo);
}

  四、看看两种写法对应的IL

  我们说过,经过编译的Expression Tree就是一段IL代码,而IL Emit则直接反映了IL的执行流程。要判断两者在性能方面孰优孰劣,我们只需要看看Expression Tree最终被转换成怎样的IL。我们现在的做法是动态生成一个程序集,将Expression Tree部分定义到一个方法之中。虽然IL Emit已经是真实底反映了底层的IL代码,但是为了我们的比较更加直观,我们也将IL Emit的部分也写入相应的方法。

  为此我们在一个Console应用中的Main方法编写了如下的代码:动态创建了名称为Artech.EmitVsExpression的程序集,其中定义了同名的模块。一个唯一的类型Program定义其中,其中定义了四个静态方法:GetPropertyValueViaExpression、SetPropertyValueViaExpression、GetPropertyValueViaEmit和GetPropertyValueViaEmit。而方法体部分则是上面Expression Tree和IL Emit定义的内容。最后这个程序集被保存为一个同名的.dll文件。

 
static
void
Main()
{
var property
=
typeof
(IFoo).GetProperty(
"
Bar
"
);
var assemblyBuilder
=
AppDomain.CurrentDomain.DefineDynamicAssembly(
new
AssemblyName(
"
Artech.EmitVsExpression
"
), AssemblyBuilderAccess.RunAndSave);
var moduleBuilder
=
assemblyBuilder.DefineDynamicModule(
"
Artech.EmitVsExpression
"
,
"
Artech.EmitVsExpression.dll
"
);
var typeBuilder
=
moduleBuilder.DefineType(
"
Program
"
);
//
GetPropertyValueViaExpression
var methodBuilder
=
typeBuilder.DefineMethod(
"
GetPropertyValueViaExpression
"
, MethodAttributes.Static
|
MethodAttributes.Public,
typeof
(Bar),
new
Type[] {
typeof
(IFoo) });
var target
=
Expression.Parameter(
typeof
(IFoo));
var getPropertyValue
=
Expression.Property(target, property);
Expression.Lambda
<
Func
<
IFoo, Bar
>>
(getPropertyValue, target).CompileToMethod(methodBuilder);
//
SetPropertyValueViaExpression
methodBuilder
=
typeBuilder.DefineMethod(
"
SetPropertyValueViaExpression
"
, MethodAttributes.Static
|
MethodAttributes.Public,
typeof
(
void
),
new
Type[] {
typeof
(IFoo),
typeof
(Bar) });
target
=
Expression.Parameter(
typeof
(IFoo));
var propertyValue
=
Expression.Parameter(
typeof
(Bar));
var setPropertyValue
=
Expression.Call(target, property.GetSetMethod(), propertyValue);
Expression.Lambda
<
Action
<
IFoo, Bar
>>
(setPropertyValue, target, propertyValue).CompileToMethod(methodBuilder);
//
GetPropertyValueViaEmit
methodBuilder
=
typeBuilder.DefineMethod(
"
GetPropertyValueViaEmit
"
, MethodAttributes.Static
|
MethodAttributes.Public,
typeof
(Bar),
new
Type[] {
typeof
(IFoo) });
ILGenerator ilGenerator
=
methodBuilder.GetILGenerator();
ilGenerator.Emit(OpCodes.Ldarg_0);
ilGenerator.EmitCall(OpCodes.Callvirt, property.GetGetMethod(),
null
);
ilGenerator.Emit(OpCodes.Ret);
//
SetPropertyValueViaEmit
methodBuilder
=
typeBuilder.DefineMethod(
"
SetPropertyValueViaEmit
"
, MethodAttributes.Static
|
MethodAttributes.Public,
typeof
(
void
),
new
Type[] {
typeof
(IFoo),
typeof
(Bar) });
ilGenerator
=
methodBuilder.GetILGenerator();
ilGenerator.Emit(OpCodes.Ldarg_0);
ilGenerator.Emit(OpCodes.Ldarg_1);
ilGenerator.EmitCall(OpCodes.Callvirt, property.GetSetMethod(),
null
);
ilGenerator.Emit(OpCodes.Ret);
typeBuilder.CreateType();
assemblyBuilder.Save(
"
Artech.EmitVsExpression.dll
"
);
}

  现在我们通过IL Disassembler打开这个.dll文件,看看四个静态方法的IL代码。下面是用于用于获取属性值的GetPropertyValueViaExpression和GetPropertyValueViaEmit方法,我们可以看出它们具有完全一致的方式体。

 
.method
public
static
class
[EmitVsExpressionTree]Bar
GetPropertyValueViaExpression(
class
[EmitVsExpressionTree]IFoo A_0) cil managed
{
//
Code size 7 (0x7)
.maxstack
1
IL_0000: ldarg.
0
IL_0001: callvirt instance
class
[EmitVsExpressionTree]Bar [EmitVsExpressionTree]IFoo::get_Bar()
IL_0006: ret
}
//
end of method Program::GetPropertyValueViaExpression 上海徐汇企业网站设计与制作="color: #008000;">
.method
public
static
class
[EmitVsExpressionTree]Bar
GetPropertyValueViaEmit(
class
[EmitVsExpressionTree]IFoo A_0) cil managed
{
//
Code size 7 (0x7)
.maxstack
1
上海企业网站设计与制作>
IL_0000: ldarg.
0
IL_0001: callvirt instance
class
[EmitVsExpressionTree]Bar [EmitVsExpressionTree]IFoo::get_Bar()
IL_0006: ret
}
//
end of method Program::GetPropertyValueViaEmit

  下面是用于对属性进行赋值的两个静态方法:SetPropertyValueViaExpression和SetPropertyValueViaEmit,毫无疑问它们之间也没有差异。到现在,你还在怀疑两种之间在性能上孰优孰劣吗?

 
method
public
static
void
SetPropertyValueViaExpression( class
[EmitVsExpressionTree]IFoo A_0,
class
[EmitVsExpressionTree]Bar A_1) cil managed
{
//
Code size 8 (0x8)
.maxstack
2
IL_0000: ldarg.
0
IL_0001: ldarg.
1
IL_0002: callvirt instance
void
[EmitVsExpressionTree]IFoo::set_Bar(
class
[EmitVsExpressionTree]Bar)
IL_0007: ret
}
//
end of method Program::SetPropertyValueViaExpression
.method
public
static
void
SetPropertyValueViaEmit(
class
[EmitVsExpressionTree]IFoo A_0,
class
[EmitVsExpressionTree]Bar A_1) cil managed
{
//
Code size 8 (0x8)
.maxstack
2
IL_0000: ldarg.
0
IL_0001: ldarg.
1
IL_0002: callvirt instance
void
[EmitVsExpressionTree]IFoo::set_Bar(
class
[EmitVsExpressionTree]Bar)
IL_0007: ret
}
//
end of method Program::SetPropertyValueViaEmit

  既然在IL上它们没有差别,那么它们就是两对等效的方法。如果你通过Reflector来打开我们生成的.dll,你会清晰地看到这真的是两对完全一致的方法。

 
internal
上海徐汇企业网站制作>
class
Program
{
//
Methods
public
static
Bar GetPropertyValueViaEmit(IFoo foo1)
{
return
foo1.Bar;
}
public
static
Bar GetPropertyValueViaExpression(IFoo foo1)
{
span>return foo1.Bar;
}
public static void SetPropertyValueViaEmit(IFoo foo1, Bar bar1)
{
foo1.Bar
= bar1;
}
public static void SetPropertyValueViaExpression(IFoo foo1, Bar bar1)
{
foo1.Bar
= bar1;
}
}

转载于:https://www.cnblogs.com/waw/archive/2011/10/19/2218087.html

你可能感兴趣的文章
《代码敲不队》第四次作业:项目需求调研与分析
查看>>
菜鸡互啄队—— 团队合作
查看>>
HttpWebRequest的GetResponse或GetRequestStream偶尔超时 + 总结各种超时死掉的可能和相应的解决办法...
查看>>
SparseArray
查看>>
第二章
查看>>
android背景选择器selector用法汇总
查看>>
[转]Paul Adams:为社交设计
查看>>
showdialog弹出窗口刷新问题
查看>>
java
查看>>
Vue.js连接后台数据jsp页面  ̄▽ ̄
查看>>
关于程序的单元测试
查看>>
mysql内存优化
查看>>
都市求生日记第一篇
查看>>
Java集合---HashMap源码剖析
查看>>
SQL优化技巧
查看>>
thead 固定,tbody 超出滚动(附带改变滚动条样式)
查看>>
Dijkstra算法
查看>>
css 动画 和 响应式布局和兼容性
查看>>
csrf 跨站请求伪造相关以及django的中间件
查看>>
MySQL数据类型--与MySQL零距离接触2-11MySQL自动编号
查看>>