C# 如何将默认值(TimeSpan)作为可选参数的默认值发出
我想发射动态方法,与下面完全相同:C# 如何将默认值(TimeSpan)作为可选参数的默认值发出,c#,dynamic,reflection,reflection.emit,C#,Dynamic,Reflection,Reflection.emit,我想发射动态方法,与下面完全相同: void Foo(TimeSpan ts = default(TimeSpan)) 通过使用ildasm,我可以看到它被编译为nullref。 然而,从我所能得到的情况来看,如果我想通过发出代码来实现同样的效果,我可以调用名为ParameterBuilder.SetConstant,当可选值类型为TimeSpan时,它将抛出异常。 我甚至反编译了SetConstant方法,它显式地处理DateTime(但不是TimeSpan)。Nullref也是不可接受的。
void Foo(TimeSpan ts = default(TimeSpan))
通过使用ildasm
,我可以看到它被编译为nullref
。
然而,从我所能得到的情况来看,如果我想通过发出代码来实现同样的效果,我可以调用名为ParameterBuilder.SetConstant
,当可选值类型为TimeSpan
时,它将抛出异常。
我甚至反编译了SetConstant
方法,它显式地处理DateTime
(但不是TimeSpan
)。Nullref也是不可接受的。从该代码中,似乎无法将默认值(TimeSpan
)设置为默认值。
有人能帮忙吗?这很难,需要大量使用反射来解决.net framework的限制 正如您所指出的,您可以反汇编ParameterBuilder.setConstant。此方法调用一个内部方法:
[SecuritySafeCritical]
public virtual void SetConstant(object defaultValue)
{
TypeBuilder.SetConstantValue(this.m_methodBuilder.GetModuleBuilder(), this.m_pdToken.Token, (this.m_iPosition == 0) ? this.m_methodBuilder.ReturnType : this.m_methodBuilder.m_parameterTypes[this.m_iPosition - 1], defaultValue);
}
您还可以反汇编它,并查看从何处引发异常(当类型为值类型时):
以这种方式设置默认值不会更新stacksize,这意味着您必须在获取ILGenerator后立即手动(再次通过反射)设置它:
FieldInfo fi = typeof(ILGenerator).GetField("m_maxStackSize", BindingFlags.NonPublic | BindingFlags.Instance);
fi.SetValue(ilgen, 8);
这将生成以下IL:
.method public hidebysig static
void Foo (
[opt] valuetype [mscorlib]System.TimeSpan ts
) cil managed
{
.param [1] = nullref
// Method begins at RVA 0x2050
// Code size 1 (0x1)
.maxstack 8
IL_0000: ret
} // end of method MyClass::Foo
这与您提供的C#编译到的内容是一样的。根据您想要实现的内容,确切地说,可能有一种更简单的方法。如果调用
ParameterBuilder.DefineParameter(1,ParameterAttributes.Optional,“Foo”)
,则生成的参数将声明为可选,但没有明确的默认值。在C#中使用此程序集时,您将无法获得默认值的IntelliSense,但编译器仍允许您在不显式提供值的情况下调用该方法,如果您这样做,它将通过default(TimeSpan)
生成的IL与C#编译器将生成的IL不同(因为缺少参数初始化),我只能猜测其他.NET语言将如何使用这种声明,但它确实节省了System.Reflection.Emit
(生成的IL通过了验证——运行时本身对默认声明不做任何处理)
请注意,正是因为运行时不使用默认值声明(需要任何工具这样做),所以在动态方法中发出默认值是一种奇怪的做法,应该很少甚至没有实际的应用程序,因为知道调用该方法的任何代码也应该知道要传递的值(在保存到磁盘的程序集中定义它们是有意义的,编译器可以读取)
如果该方法确实是动态的,那么您可能希望生成该方法的多个重载,一个带参数,一个不带参数(一个不带参数的可以调用另一个)。这与具有可选参数的方法具有相同的效果,并且对于动态调用方来说也更易于处理。添加代码-这是一个显示问题并实际编译和运行的最小示例。请注意,
ParameterBuilder.SetConstant
的文档明确提到了支持的类型,以及TimeSp根本不包括
(根据规范,它不是标准类型,就像十进制)还有,你为什么要这样做?应该如何使用动态方法?你是否使用它来编译到稍后引用的程序集?默认值是编译时特性,因此除非你从普通C代码调用该方法(无反射等),默认值没有任何意义。你想做什么,为什么你认为默认参数是一个很好的方法?我发现你的问题非常有趣,但你确实应该添加一个你尝试过的代码示例,以及调用SetConstant时得到的异常。好的问题往往会吸引大量好的答案,我认为我认为这是一个非常有趣的问题。如果下面的答案中有一个真的回答了你的问题,请考虑接受其中的任何一个。这将为下一个访问这个橡树园的人节省时间
FieldInfo fi = typeof(ILGenerator).GetField("m_maxStackSize", BindingFlags.NonPublic | BindingFlags.Instance);
fi.SetValue(ilgen, 8);
.method public hidebysig static
void Foo (
[opt] valuetype [mscorlib]System.TimeSpan ts
) cil managed
{
.param [1] = nullref
// Method begins at RVA 0x2050
// Code size 1 (0x1)
.maxstack 8
IL_0000: ret
} // end of method MyClass::Foo