我想发出与下面完全相同的动态方法:
void Foo(TimeSpan ts = default(TimeSpan))
使用ildasm
,我可以看到它已被编译为nullref
。
但是,根据我能得到的结果,如果我想通过Emit代码实现相同的功能,我可以调用名为ParameterBuilder.SetConstant
的方法,当可选值类型为TimeSpan
时,它将抛出异常。
我甚至反编译了SetConstant
方法,它明确处理DateTime
(但不是TimeSpan
)。 Nullref也是不可接受的。从该代码开始,似乎无法将默认值(TimeSpan
)设置为默认值。
有人可以帮忙吗?
答案 0 :(得分:1)
这很难,需要大量使用反射来解决.net框架的局限性。
正如您所指出的,您可以反汇编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);
}
你也可以反汇编,看看抛出异常的位置(当type是值类型时):
if (destType.IsValueType && (!destType.IsGenericType || !(destType.GetGenericTypeDefinition() == typeof(Nullable<>))))
{
throw new ArgumentException(Environment.GetResourceString("Argument_ConstantNull"));
}
TypeBuilder.SetConstantValue(module.GetNativeHandle(), tk, 18, null);
幸运的是,您可以在此处调用相同的方法,但可以从mscorlib动态调用:
AssemblyName aName = new AssemblyName("DynamicAssemblyExample");
AssemblyBuilder ab =
AppDomain.CurrentDomain.DefineDynamicAssembly(aName, AssemblyBuilderAccess.RunAndSave);
ModuleBuilder mb =
ab.DefineDynamicModule(aName.Name, aName.Name + ".dll");
TypeBuilder tb = mb.DefineType("MyClass", TypeAttributes.Public);
MethodBuilder meb = tb.DefineMethod("Foo", MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, typeof(void), new Type[] { typeof(TimeSpan) });
ParameterBuilder pb = meb.DefineParameter(1, ParameterAttributes.Optional | ParameterAttributes.HasDefault, "ts");
MethodInfo getNativeHandle = typeof(ModuleBuilder).GetMethod("GetNativeHandle", BindingFlags.NonPublic | BindingFlags.Instance);
object nativeHandle = getNativeHandle.Invoke(mb, new object[0]);
int tk = pb.GetToken().Token;
MethodInfo setConstantValue = typeof(TypeBuilder).GetMethods(BindingFlags.NonPublic | BindingFlags.Static).Where(mi => mi.Name == "SetConstantValue" && mi.GetParameters().Last().ParameterType.IsPointer).First();
setConstantValue.Invoke(pb, new object[] { nativeHandle, tk, /* CorElementType.Class: */ 18, null });
ILGenerator ilgen = meb.GetILGenerator();
FieldInfo fi = typeof(ILGenerator).GetField("m_maxStackSize", BindingFlags.NonPublic | BindingFlags.Instance);
fi.SetValue(ilgen, 8);
ilgen.Emit(OpCodes.Ret);
tb.CreateType();
ab.Save("DynamicAssemblyExample.dll");
以这种方式设置默认值不会更新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#编译的内容相同。
答案 1 :(得分:1)
根据您想要实现的目标,确切地说,可能有一种更简单的方法。如果您调用ParameterBuilder.DefineParameter(1, ParameterAttributes.Optional, "Foo")
,结果参数将被声明为可选参数,但没有显式默认值。在C#中使用此程序集时,您将无法获得默认值的IntelliSense,但编译器仍允许您在不显式提供值的情况下调用该方法,如果这样做,它将通过default(TimeSpan)
。< / p>
生成的IL与C#编译器生成的内容不同(因为缺少参数初始化),我只能猜测其他.NET语言会用这样的声明做什么,但它确实可以节省一些非常难看的在System.Reflection.Emit
的内部内部进行了修改(并且生成的IL通过了验证 - 运行时本身对默认声明没有任何作用)。
请注意,正是因为运行时没有对默认值声明做任何事情(需要任何工具这样做)在动态方法中发出默认值是一种奇怪的做法,应该几乎没有实际的应用程序,因为任何知道调用该方法的代码也应该知道要传递什么值(在保存到磁盘的程序集中定义它们是有意义的,编译器可以读取它)。
如果该方法确实是动态的,您可能希望生成该方法的多个重载,一个带参数,一个没有(而一个没有可以调用另一个)。这实现了与带有可选参数的方法相同的效果,并且动态调用者也可以更轻松地处理。