C#类型的运算符的效率(或其在MSIL中的任何表示)

我知道过早的优化是所有邪恶的母亲。 但是,我正在定义一个generics方法,它使用Reflection来检索其generics类型的元数据,并想知道是否多次调用typeof(T) ,如下面的代码片段所示:

 private static Dictionary elementProperties; private static T MakeElement(SqlDataReader reader) where T : class, new() { PropertyInfo[] properties; if (elementProperties.ContainsKey(typeof(T))) properties = elementProperties[typeof(T)]; else properties = elementProperties[typeof(T)] = typeof(T).GetProperties(); // more code... } 

…比将类型对象存储到变量中的效率低,如下面的代码片段所示:

 private static Dictionary elementProperties; private static T MakeElement(SqlDataReader reader) where T : class, new() { PropertyInfo[] properties; Type type = typeof(T); if (elementProperties.ContainsKey(type)) properties = elementProperties[type]; else properties = elementProperties[type] = type.GetProperties(); // more code... } 

…?

如果我正确理解编译器理论(我认为我这样做),这个问题可以简化为以下问题:

当JIT编译器实例化generics类型时,它是否替换了[ typeof(T)的MSIL表示的所有实例] …

  1. …对实际类型对象的引用? (好)
  2. …一个方法调用/子程序/什么检索对实际类型对象的引用? (坏)
  3. …一个方法调用/子例程/什么构造一个类型对象并返回它的引用? (非常非常糟糕)

一点直觉应该告诉你,声明一个变量的单个实例来保存GetType()的结果,然后在整个rest方法中使用它会更有效(并且更具可读性)

这是两种方法的IL:

MakeElement 1:

 Icall System.Type.GetTypeFromHandle callvirt System.Collections.Generic.Dictionary.ContainsKey brfalse.s IL_002F ldarg.0 ldfld elementProperties ldtoken 01 00 00 1B call System.Type.GetTypeFromHandle callvirt System.Collections.Generic.Dictionary.get_Item pop br.s IL_0053 ldarg.0 ldfld elementProperties ldtoken 01 00 00 1B call System.Type.GetTypeFromHandle ldtoken 01 00 00 1B call System.Type.GetTypeFromHandle call System.Type.GetProperties callvirt System.Collections.Generic.Dictionary.set_Item 

MakeElement 2:

 call System.Type.GetTypeFromHandle stloc.0 ldarg.0 ldfld elementProperties ldloc.0 callvirt System.Collections.Generic.Dictionary.ContainsKey brfalse.s IL_0028 ldarg.0 ldfld elementProperties ldloc.0 callvirt System.Collections.Generic.Dictionary.get_Item pop br.s IL_003A ldarg.0 ldfld elementProperties ldloc.0 ldloc.0 callvirt System.Type.GetProperties callvirt System.Collections.Generic.Dictionary.set_Item 

通过在局部变量中声明它,可以保存对System.Type.GetTypeFromHandle的1或2个调用。 我不确定JIT的过程是不会编译出来的,但我个人会更加相信编译器来优化IL,就像JIT’er那样,但那只是我。

我不知道这是否是C#或JIT编译器的记录行为 – 依赖于关键问题中的无证行为通常不是一个好主意。 原则上,这是常量传播问题的一个版本(因为T不能在方法的范围内改变),并且优化编译器应该能够解决这个问题。

如果性能对您至关重要,您可能希望通过存储对类型信息的本地引用来确保所需的行为。

如果您只是好奇,我建议您阅读生成的IL和/或对代码执行一些基准测试,以了解这种更改在您的特定方案中会有什么样的真实差异。

除了性能之外,我实际上找到了代码的版本,其中使用变量来引用类型参数,使其比typeof(T)重复的版本更易读和易懂。

生成的MSIL显示两者不同, typeof(T)未被提升为局部变量。 这意味着它会将T的类型元数据加载到堆栈中,并在每次使用时调用Type.GetTypeFromHandle 。 我不知道为什么它选择不用/optimize+解除这个,但我认为这是编译器的特权。

两个代码块之间的一个实际区别是typeof(T) 基本上是一个常量表达式,而你的局部变量type是可变的。 这可能不是未来开发人员可能破坏的预期语义。