关于Expression Tree和IL Emit的所谓的"性能差别"

本文探讨了ExpressionTree和ILEmit在属性赋值和取值操作中的性能表现,通过对比两者生成的IL代码,证实了它们在性能上并无本质区别。

昨天写了《三种属性操作性能比较》,有个网友写信问我一个问题:从性能上看,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的基础上推出了很多语言层面的特性,比如自动实现属性:

   1: public class Foo
   2: {
   3:     public Bar Bar{get;set;}
   4:     public Foo()
   5:     {
   6:         this.Bar = new Bar();
   7:     }
   8: }

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

   1: public class Foo
   2: {
   3:     private Bar _bar;
   4:     public Bar Bar
   5:     {
   6:         get{return _bar;}
   7:         set{_bar = value;}
   8:     }
   9:     public Foo()
  10:     {
  11:         _bar = new Bar();
  12:     }
  13: }

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的可读写的属性。

   1: public interface IFoo
   2: {
   3:     Bar{get;set;}
   4: }
   5: public class Bar{}

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

   1: public static void SetPropertyValueViaExpression(IFoo foo, Bar bar)
   2: {
   3:     var property = typeof(IFoo).GetProperty("Bar");
   4:     var target = Expression.Parameter(typeof(IFoo));
   5:     var propertyValue = Expression.Parameter(typeof(Bar));
   6:     var setPropertyValue = Expression.Call(target, property.GetSetMethod(), propertyValue);
   7:     var setAction= Expression.Lambda<Action<IFoo, Bar>>(setPropertyValue, target, propertyValue).Compile();
   8:     setAction(foo, bar);
   9: }

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

   1: public static void SetPropertyValueViaEmit(IFoo foo, Bar bar)
   2: {
   3:     var property = typeof(IFoo).GetProperty("Bar");
   4:     DynamicMethod method = new DynamicMethod("SetValue", null, new Type[] { typeof(IFoo), typeof(Bar) });
   5:     ILGenerator ilGenerator = method.GetILGenerator();
   6:     ilGenerator.Emit(OpCodes.Ldarg_0);
   7:     ilGenerator.Emit(OpCodes.Ldarg_1);
   8:     ilGenerator.EmitCall(OpCodes.Callvirt, property.GetSetMethod(), null);
   9:     ilGenerator.Emit(OpCodes.Ret);
  10:  
  11:     method.DefineParameter(1, ParameterAttributes.In, "obj");
  12:     method.DefineParameter(2, ParameterAttributes.In, "value");
  13:     var setAction = (Action<IFoo, Bar>)method.CreateDelegate(typeof(Action<IFoo, Bar>));
  14:     setAction(foo, bar);
  15: }

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

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

   1: public static Bar GetPropertyValueViaExpression(IFoo foo)
   2: {
   3:     var property = typeof(IFoo).GetProperty("Bar");
   4:     var target = Expression.Parameter(typeof(IFoo));
   5:     var getPropertyValue = Expression.Property(target, property);
   6:     var getFunc = Expression.Lambda<Func<IFoo, Bar>>(getPropertyValue, target).Compile();
   7:     return getFunc(foo);
   8: }

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

   1: public static Bar GetPropertyValueViaEmit(IFoo foo)
   2: {
   3:     var property = typeof(IFoo).GetProperty("Bar");
   4:     DynamicMethod method = new DynamicMethod("GetValue", typeof(Bar), new Type[] { typeof(IFoo) });
   5:  
   6:     ILGenerator ilGenerator = method.GetILGenerator();
   7:     ilGenerator.Emit(OpCodes.Ldarg_0);
   8:     ilGenerator.EmitCall(OpCodes.Callvirt, property.GetGetMethod(), null);
   9:     ilGenerator.Emit(OpCodes.Ret);
  10:  
  11:     method.DefineParameter(1, ParameterAttributes.In, "target");
  12:     var getFunc = (Func<IFoo, Bar>)method.CreateDelegate(typeof(Func<IFoo, Bar>));
  13:     return getFunc(foo);
  14: }

四、看看两种写法对应的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文件。

   1: static void Main()
   2: {
   3:     var property        = typeof(IFoo).GetProperty("Bar");
   4:     var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("Artech.EmitVsExpression"), AssemblyBuilderAccess.RunAndSave);
   5:     var moduleBuilder   = assemblyBuilder.DefineDynamicModule("Artech.EmitVsExpression", "Artech.EmitVsExpression.dll");
   6:     var typeBuilder     = moduleBuilder.DefineType("Program");
   7:  
   8:     //GetPropertyValueViaExpression
   9:     var methodBuilder       = typeBuilder.DefineMethod("GetPropertyValueViaExpression", MethodAttributes.Static | MethodAttributes.Public, typeof(Bar), new Type[] { typeof(IFoo) });
  10:     var target              = Expression.Parameter(typeof(IFoo));
  11:     var getPropertyValue    = Expression.Property(target, property);
  12:     Expression.Lambda<Func<IFoo, Bar>>(getPropertyValue, target).CompileToMethod(methodBuilder);
  13:  
  14:     //SetPropertyValueViaExpression
  15:     methodBuilder           = typeBuilder.DefineMethod("SetPropertyValueViaExpression", MethodAttributes.Static | MethodAttributes.Public, typeof(void), new Type[] { typeof(IFoo), typeof(Bar) });
  16:     target                  = Expression.Parameter(typeof(IFoo));
  17:     var propertyValue       = Expression.Parameter(typeof(Bar));
  18:     var setPropertyValue    = Expression.Call(target, property.GetSetMethod(), propertyValue);
  19:     Expression.Lambda<Action<IFoo, Bar>>(setPropertyValue, target, propertyValue).CompileToMethod(methodBuilder);
  20:  
  21:     //GetPropertyValueViaEmit
  22:     methodBuilder           = typeBuilder.DefineMethod("GetPropertyValueViaEmit", MethodAttributes.Static| MethodAttributes.Public, typeof(Bar), new Type[] { typeof(IFoo) });
  23:     ILGenerator ilGenerator = methodBuilder.GetILGenerator();
  24:     ilGenerator.Emit(OpCodes.Ldarg_0);
  25:     ilGenerator.EmitCall(OpCodes.Callvirt, property.GetGetMethod(), null);
  26:     ilGenerator.Emit(OpCodes.Ret);
  27:  
  28:     //SetPropertyValueViaEmit
  29:     methodBuilder   = typeBuilder.DefineMethod("SetPropertyValueViaEmit", MethodAttributes.Static | MethodAttributes.Public, typeof(void), new Type[] { typeof(IFoo), typeof(Bar) });
  30:     ilGenerator     = methodBuilder.GetILGenerator();
  31:     ilGenerator.Emit(OpCodes.Ldarg_0);
  32:     ilGenerator.Emit(OpCodes.Ldarg_1);
  33:     ilGenerator.EmitCall(OpCodes.Callvirt, property.GetSetMethod(), null);
  34:     ilGenerator.Emit(OpCodes.Ret);
  35:  
  36:     typeBuilder.CreateType();
  37:     assemblyBuilder.Save("Artech.EmitVsExpression.dll");
  38: }

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

   1: .method public static class [EmitVsExpressionTree]Bar 
   2:         GetPropertyValueViaExpression(class [EmitVsExpressionTree]IFoo A_0) cil managed
   3: {
   4:   // Code size       7 (0x7)
   5:   .maxstack  1
   6:   IL_0000:  ldarg.0
   7:   IL_0001:  callvirt   instance class [EmitVsExpressionTree]Bar [EmitVsExpressionTree]IFoo::get_Bar()
   8:   IL_0006:  ret
   9: } // end of method Program::GetPropertyValueViaExpression
  10:  
  11: .method public static class [EmitVsExpressionTree]Bar 
  12:         GetPropertyValueViaEmit(class [EmitVsExpressionTree]IFoo A_0) cil managed
  13: {
  14:   // Code size       7 (0x7)
  15:   .maxstack  1
  16:   IL_0000:  ldarg.0
  17:   IL_0001:  callvirt   instance class [EmitVsExpressionTree]Bar [EmitVsExpressionTree]IFoo::get_Bar()
  18:   IL_0006:  ret
  19: } // end of method Program::GetPropertyValueViaEmit

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

   1: .method public static void  SetPropertyValueViaExpression(class [EmitVsExpressionTree]IFoo A_0,
   2:                                                           class [EmitVsExpressionTree]Bar A_1) cil managed
   3: {
   4:   // Code size       8 (0x8)
   5:   .maxstack  2
   6:   IL_0000:  ldarg.0
   7:   IL_0001:  ldarg.1
   8:   IL_0002:  callvirt   instance void [EmitVsExpressionTree]IFoo::set_Bar(class [EmitVsExpressionTree]Bar)
   9:   IL_0007:  ret
  10: } // end of method Program::SetPropertyValueViaExpression
  11:  
  12: .method public static void  SetPropertyValueViaEmit(class [EmitVsExpressionTree]IFoo A_0,
  13:                                                     class [EmitVsExpressionTree]Bar A_1) cil managed
  14: {
  15:   // Code size       8 (0x8)
  16:   .maxstack  2
  17:   IL_0000:  ldarg.0
  18:   IL_0001:  ldarg.1
  19:   IL_0002:  callvirt   instance void [EmitVsExpressionTree]IFoo::set_Bar(class [EmitVsExpressionTree]Bar)
  20:   IL_0007:  ret
  21: } // end of method Program::SetPropertyValueViaEmit

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

   1: internal class Program
   2: {
   3:     // Methods
   4:     public static Bar GetPropertyValueViaEmit(IFoo foo1)
   5:     {
   6:         return foo1.Bar;
   7:     }
   8:  
   9:     public static Bar GetPropertyValueViaExpression(IFoo foo1)
  10:     {
  11:         return foo1.Bar;
  12:     }
  13:  
  14:     public static void SetPropertyValueViaEmit(IFoo foo1, Bar bar1)
  15:     {
  16:         foo1.Bar = bar1;
  17:     }
  18:  
  19:     public static void SetPropertyValueViaExpression(IFoo foo1, Bar bar1)
  20:     {
  21:         foo1.Bar = bar1;
  22:     }
  23: } 

晚绑定场景下对象属性赋值和取值可以不需要PropertyInfo
三种属性操作性能比较:PropertyInfo + Expression Tree + Delegate.CreateDelegate
关于Expression Tree和IL Emit的所谓的"性能差别"

转载于:https://www.cnblogs.com/artech/archive/2011/03/27/ExpressTreeVsIlEmit.html

<think>好的,我现在需要回答用户关于Qt中Q_EMITemit的区别的问题。首先,我得回忆一下自己对这两个关键字的理解。记得以前在使用Qt的时候,emit是用来触发信号的,而Q_EMIT可能是一个宏,但不确定具体区别。 首先,我应该查阅Qt的官方文档或者可靠的资料来确认两者的定义。可能的情况是,Q_EMIT是为了兼容不同的编译器或者代码规范而定义的宏。比如,有些情况下,代码中可能已经存在名为emit的变量或函数,这时候使用宏可以避免冲突。 接着,想到Qt的宏定义,比如Q_SIGNALSsignals,Q_SLOTSslots,这些宏是为了避免命名冲突,特别是在使用第三方库的时候。同样的道理,Q_EMIT可能是一个替代emit的宏,用来在需要的时候替换关键字,以增强代码的可移植性。 然后,需要确认在Qt的源码或者文档中,Q_EMITemit是否实际上是等价的。比如,在Qt的头文件中,可能通过#define将Q_EMIT定义为emit,或者在某些配置下展开为空,或者有其他处理。 另外,用户的问题可能涉及到编码规范或者跨平台兼容性。有些项目可能推荐使用Q_EMIT来保持一致性,尤其是在大型项目或者需要严格命名规范的情况下。 还需要考虑历史原因,可能在旧版本的Qt中,或者在某些特定的模块中,这两个关键字有不同的表现,但随着Qt的发展,它们可能已经统一了行为。但核心区别可能只是宏替换,没有功能上的差异。 最后,总结两者的区别:Q_EMIT是Qt提供的宏,用于替代emit关键字,主要是为了避免命名冲突,增强代码的可移植性,而emit是Qt原有的关键字,用于发射信号。在功能上,两者完全相同,Q_EMIT只是emit的一个替代写法,根据项目需要选择使用。</think>在Qt框架中,`Q_EMIT``emit`的核心区别在于**代码兼容性命名规范**,二者的实际功能完全一致。以下是详细对比: --- ### 一、本质区别 | 特性 | `emit` | `Q_EMIT` | |------------|-------------------------|------------------------------| | 本质 | Qt保留关键字 | Qt定义的宏 | | 编译处理 | 直接作为关键字处理 | 宏展开为`emit` | | 典型定义 | 无(语言级别关键字) | `#define Q_EMIT emit` | | 使用场景 | 标准Qt项目 | 需要规避命名冲突的特殊场景 | --- ### 二、底层实现解析 1. **宏定义源码**(参考`qobjectdefs.h`): ```cpp #ifndef QT_NO_EMIT # define emit // 默认情况下emit是空宏 # define Q_EMIT // Q_EMIT同理 #endif ``` - 实际上在Qt编译时,`emit``Q_EMIT`都会被预处理为空白 - 信号发射的底层通过`moc`生成的代码实现,与这两个标识符无关 2. **实际代码示例**: ```cpp // 以下两种写法完全等价 emit valueChanged(100); // 传统写法 Q_EMIT valueChanged(100); // 宏写法 ``` --- ### 三、使用场景对比 #### 推荐使用`Q_EMIT`的情况: 1. **命名冲突规避**:当代码中存在第三方库定义了`emit`标识符时 ```cpp // 假设某库定义了emit变量 int emit = 10; // 导致传统emit写法编译失败 Q_EMIT signal(); // 使用宏可避免冲突 ``` 2. **代码规范要求**:项目强制使用Qt宏系统(如大型项目统一代码风格) ```cpp // 统一使用Qt宏的代码风格 Q_OBJECT Q_SIGNALS: void signal(); public slots: void slot() { Q_EMIT signal(); } ``` 3. **跨框架兼容**:需要与其它信号/槽系统(如Boost.Signals2)共存时 --- ### 四、开发实践建议 1. **新项目规范**: - 优先使用`emit`关键字,符合大多数Qt开发者习惯 - 仅在确实需要规避命名冲突时使用`Q_EMIT` 2. **旧代码维护**: - 保持现有代码风格一致性 - 混合使用时注意团队规范文档 3. **错误用法示例**: ```cpp // ❌ 错误!二者不能组合使用 Q_EMIT emit signal(); // ❌ 错误!无法通过宏改变语法 #define MY_EMIT Q_EMIT MY_EMIT signal(); ``` --- ### 五、扩展知识:其他Qt宏 | 标准关键字 | 对应宏 | 作用 | |------------|-------------|-------------------------------| | `signals` | `Q_SIGNALS` | 声明信号 | | `slots` | `Q_SLOTS` | 声明槽函数 | | `emit` | `Q_EMIT` | 发射信号 | | `foreach` | `Q_FOREACH` | 增强型循环(已被C++11 range-for替代) | 建议在复杂项目中统一使用宏版本以提高代码健壮性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值