【问题标题】:What does [opt] mean in MSIL?[opt] 在 MSIL 中是什么意思?
【发布时间】:2011-03-31 09:09:11
【问题描述】:

我发现 C# 4.0 中的“可选参数”功能非常有趣,因此我试图弄清楚它们是如何实现的。 所以我写了一个这样的方法:

private static void A(int a = 5) { }

编译它,然后在IL DASM中反编译,这是IL代码:

.method private hidebysig static void  A([opt] int32 a) cil managed
{
  .param [1] = int32(0x00000005)
  // Code size       2 (0x2)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ret
} // end of method Program::A

它的元数据中有这个:

(1) ParamToken:(08000002) 名称:a 标志:[可选] [HasDefault] (00001010) 默认值:(I4) 5

于是我顺着线索写了一个这样的方法:

private static void B([Optional, DefaultParameterValue(78)]int b) { }

编译和反编译,发现C#编译器为方法A和B生成了几乎相同的MSIL代码(名字除外)。

我们可以看到IL代码中没有属性的迹象,感觉不对,所以我写了一个这样的自定义属性:

[AttributeUsage(AttributeTargets.Parameter)]
public class MyTestAttribute : Attribute
{
}

然后像这样在方法C中使用它:

private static void C([MyTest]int c) { }

编译然后反编译,哈哈,我发现了这个:

.method private hidebysig static void  C(int32 c) cil managed
{
  .param [1]
  .custom instance void ConsoleApplication1.MyTestAttribute::.ctor() = ( 01 00 00 00 ) 
  // Code size       2 (0x2)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ret
} // end of method Program::C

方法体的第二行调用了我的自定义属性的ctor。

所以这引起了我的怀疑:

  1. [选择] 是什么意思?我的意思是出现在方法A和B的参数前面的那个。
  2. 为什么方法 C 调用了应用于其参数的 Attribute 的构造函数,而方法 A 和 B 却没有?
  3. 我似乎在元数据中找不到任何 DefaultParameterValueAttribute 的迹象,但我可以找到 OptionalAttribute 和 MyTestAttribute。这是为什么?有什么我遗漏的吗?

提前致谢。

【问题讨论】:

    标签: c# .net c#-4.0 cil il


    【解决方案1】:

    C# 编译器不需要发出属性,因为 Param 元数据表已经可以通过 Flags 列描述可选值和默认值。

    23.1.13 起ECMA 335:

    Flag            Value   Description
    -----------------------------------------------------
    In              0x0001  Parameter is [In]  
    Out             0x0002  Parameter is [Out]  
    Optional        0x0010  Parameter is optional  
    HasDefault      0x1000  Parameter has a default value  
    HasFieldMarshal 0x2000  Parameter has FieldMarshal  
    

    参数可以有一个标志值,指定它是可选的,并且有一个默认值(0x0010 | 0x1000)。具有默认值的参数将在常量元数据表中具有关联的标记。

    Constant 元数据表有一个 Parent 列,它是相关的 Param 标记,还有一个 Value 列,它是存储默认值的 blob 堆的索引。

    所以回答你的问题:

    1. [opt] 表示 Param 令牌的 Flags 列设置了 Optional 标志。
    2. 如上所述,我的猜测是 C# 编译器正在识别 Optional/DefaultParameterValue 属性并将它们简单地转换为参数标志。
    3. 编辑:C# 编译器似乎为 OptionalAttribute 发出了一个未使用的 TypeRef,尽管该参数使用了 Optional 标志。但是,它不会为 DefaultParameterValueAttribute 发出 TypeRef。发出未使用的 TypeRefs/MemberRefs 可能是一个小的编译器错误。

    【讨论】:

    • 谢谢,没想到这么快就得到答复。但你的似乎就是那个。
    • 哇,没想到发现了CSC的bug。
    • @CuiPengFei:至少是一个假定的错误。这样做可能有一个正当的理由,但我想不出一个。
    • 刚刚发现 VB.Net 编译器也可以这样做。
    【解决方案2】:

    2 / 3;编译器将一些属性解释为 IL 元数据,而不是真正的属性;看起来就是这种情况; [Serializable] 是另一个例子。默认的数据在那里:Default: (I4) 5 - 并非代码中的所有属性都成为元数据中的属性(再次,我在这里查看[Serializable]


    关于[Serializable] (cmets) 的那一点;这是一个例子:

    [Description("abc")]
    class Foo { }
    
    [Serializable]
    class Bar { }
    

    核心IL是:

    .class private auto ansi beforefieldinit Foo
        extends [mscorlib]System.Object
    {
        .custom instance void [System]System.ComponentModel.DescriptionAttribute::.ctor(string) = { string('abc') }
        .method public hidebysig specialname rtspecialname instance void .ctor() cil managed
        {
        }
    
    }
    .class private auto ansi serializable beforefieldinit Bar
        extends [mscorlib]System.Object
    {
        .method public hidebysig specialname rtspecialname instance void .ctor() cil managed
        {
        }
    
    }
    

    Foo(对于一些任意属性),我们得到:

    .custom instance void [System]System.ComponentModel.DescriptionAttribute::.ctor(string) = { string('abc') }
    

    但是这不适用于[Serializable];相反,这是类型的一部分:

    .class private auto ansi serializable beforefieldinit Bar
    

    【讨论】:

    • 谢谢,这解释了很多。顺便说一句,我在谷歌搜索“可选参数”时看到了你的帖子,:)
    • 我只是测试了一下,实际上Serializable仍然出现在元数据的reftype部分。
    • @CuiPengFei - 更新说明
    • 从 ECMA-335 开始,后者将是一个 TypeDef,其标志值设置为 Serializable (0x00002000)。
    • @CuiPengFei:确实,从这个例子中,我还注意到编译器正在为属性发出一个 TypeRef,尽管它似乎没有被引用。
    猜你喜欢
    • 2013-03-31
    • 1970-01-01
    • 2012-09-20
    • 2011-08-12
    • 2017-06-11
    • 2018-03-05
    • 2023-03-27
    • 2016-08-17
    相关资源
    最近更新 更多