首先,CLR 对此不提供支持。它必须由编译器实现。你可以从一个小测试程序中看到一些东西:
class Program {
static void Main(string[] args) {
Test();
Test(42);
}
static void Test(int value = 42) {
}
}
反编译成:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 15 (0xf)
.maxstack 8
IL_0000: ldc.i4.s 42
IL_0002: call void Program::Test(int32)
IL_0007: ldc.i4.s 42
IL_0009: call void Program::Test(int32)
IL_000e: ret
} // end of method Program::Main
.method private hidebysig static void Test([opt] int32 'value') cil managed
{
.param [1] = int32(0x0000002A)
// Code size 1 (0x1)
.maxstack 8
IL_0000: ret
} // end of method Program::Test
请注意编译器完成后两个调用语句之间没有任何区别。是编译器应用了默认值并在调用站点这样做。
另请注意,当 Test() 方法实际存在于另一个程序集中时,这仍然需要工作。这意味着需要在元数据中编码默认值。注意.param 指令是如何做到这一点的。 CLI 规范 (Ecma-335) 在第 II.15.4.1.4 节中记录了它
该指令在元数据中存储一个与方法参数编号 Int32 关联的常量值,
见§II.22.9。虽然 CLI 要求为参数提供一个值,但某些工具可以使用
此属性的存在表明该工具而不是用户旨在提供值
参数。与 CIL 指令不同,.param 使用索引 0 来指定方法的返回值,
index 1 指定方法的第一个参数, index 2 指定方法的第二个参数
方法等等。
[注意:CLI 对这些值不附加任何语义——这完全取决于编译器
实现他们希望的任何语义(例如,所谓的默认参数值)。尾注]
引用的第 II.22.9 节详细介绍了常量值的含义。最相关的部分:
类型应为以下之一:ELEMENT_TYPE_BOOLEAN、ELEMENT_TYPE_CHAR、
ELEMENT_TYPE_I1、ELEMENT_TYPE_U1、ELEMENT_TYPE_I2、ELEMENT_TYPE_U2、
ELEMENT_TYPE_I4、ELEMENT_TYPE_U4、ELEMENT_TYPE_I8、ELEMENT_TYPE_U8、
ELEMENT_TYPE_R4、ELEMENT_TYPE_R8 或 ELEMENT_TYPE_STRING; 或
ELEMENT_TYPE_CLASS 的值为 0
所以这就是责任停止的地方,甚至没有好方法来引用匿名帮助方法,因此某种代码提升技巧也无法工作。
值得注意的是,这不是问题,您始终可以为引用类型的参数实现任意默认值。例如:
private void Process(Foo f = null)
{
if (f == null) f = new Foo();
}
这很合理。以及您在方法而不是调用站点中想要的那种代码。