C#';s类型的运算符(或其在MSIL中的任何表示)

本文关键字:MSIL 表示 任何 运算符 类型 | 更新日期: 2023-09-27 17:59:37

我知道过早优化是万恶之母。然而,我正在定义一个通用方法,该方法使用反射来检索其通用类型的元数据,并想知道是否像下面的代码片段中那样多次调用typeof(T)

private static Dictionary<Type, PropertyInfo[]> elementProperties;
private static T MakeElement<T>(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<Type, PropertyInfo[]> elementProperties;
private static T MakeElement<T>(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编译器实例化泛型类型时,它是否将[无论typeof(T)的MSIL表示是什么]的每个实例都替换为。。。

  1. 。。。对实际类型对象的引用?(好)
  2. 。。。方法调用/子例程/检索对实际类型对象的引用的任何东西?(坏)
  3. 。。。构造类型对象并返回对它的引用的方法调用/子例程/任何东西?(非常非常糟糕)

C#';s类型的运算符(或其在MSIL中的任何表示)

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

以下是两种方法的IL:

MakeElement 1:

Icall       System.Type.GetTypeFromHandle
callvirt    System.Collections.Generic.Dictionary<System.Type,System.Reflection.PropertyInfo[]>.ContainsKey
brfalse.s   IL_002F
ldarg.0     
ldfld       elementProperties
ldtoken     01 00 00 1B 
call        System.Type.GetTypeFromHandle
callvirt    System.Collections.Generic.Dictionary<System.Type,System.Reflection.PropertyInfo[]>.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<System.Type,System.Reflection.PropertyInfo[]>.set_Item

MakeElement 2:

call        System.Type.GetTypeFromHandle
stloc.0     
ldarg.0     
ldfld       elementProperties
ldloc.0     
callvirt    System.Collections.Generic.Dictionary<System.Type,System.Reflection.PropertyInfo[]>.ContainsKey
brfalse.s   IL_0028
ldarg.0     
ldfld       elementProperties
ldloc.0     
callvirt    System.Collections.Generic.Dictionary<System.Type,System.Reflection.PropertyInfo[]>.get_Item
pop         
br.s        IL_003A
ldarg.0     
ldfld       elementProperties
ldloc.0     
ldloc.0     
callvirt    System.Type.GetProperties
callvirt    System.Collections.Generic.Dictionary<System.Type,System.Reflection.PropertyInfo[]>.set_Item

通过在局部变量中声明System.Type.GetTypeFromHandle,可以保存对它的1或2个调用。我不确定JIT'ing过程是否不会编译出这些,但我个人更相信编译器会优化IL,以解决JITer上的问题,但这只是我自己。

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

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

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

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

如果你只是好奇,我建议你阅读生成的IL和/或对代码执行一些基准测试,看看这样的更改在你的特定场景中会有什么现实世界的不同。

撇开性能不谈,我实际上发现,使用变量引用类型参数的代码版本比typeof(T)贯穿始终的版本更可读、更容易理解。