9

我发现 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。这是为什么?有什么我想念的吗?

提前致谢。

4

2 回答 2

10

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

ECMA 335中的 23.1.13 开始:

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)。具有默认值的参数将在常量元数据表中具有关联的标记。

常量元数据表有一Parent列是相关的参数标记,一Value列是存储默认值的 blob 堆的索引。

所以回答你的问题:

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

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


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

[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
于 2011-03-31T09:24:28.343 回答