.net 默认参数和反射:如果ParameterInfo.IsOptional,那么DefaultValue是否始终可靠?

.net 默认参数和反射:如果ParameterInfo.IsOptional,那么DefaultValue是否始终可靠?,.net,reflection,compiler-construction,.net,Reflection,Compiler Construction,我正在研究ParameterInfo.IsOptional是如何定义的(我正在向内部IOC框架添加默认参数支持),在我看来,如果为true,则无法保证ParameterInfo.DefaultValue(或者确实是ParameterInfo.RawDefaultValue)实际上是要应用的默认值 如果查看,似乎可以在IL中定义一个可选参数,但不提供默认值(假定必须显式提供ParameterAttributes.HasDefault)。也就是说,可能导致参数类型为,例如,Int32,Paramet

我正在研究
ParameterInfo.IsOptional
是如何定义的(我正在向内部IOC框架添加默认参数支持),在我看来,如果为true,则无法保证
ParameterInfo.DefaultValue
(或者确实是
ParameterInfo.RawDefaultValue
)实际上是要应用的默认值

如果查看,似乎可以在IL中定义一个可选参数,但不提供默认值(假定必须显式提供
ParameterAttributes.HasDefault
)。也就是说,可能导致参数类型为,例如,
Int32
ParameterInfo.IsOptional
为真,但
ParameterInfo.DefaultValue
为空

我的语言是C#,因此我可以研究编译器的功能。基于此,我可以进行如下简单测试(
parameter
这里是一个
ParameterInfo
实例,该方法旨在返回一个实例,用作参数的运行时参数):

但是我认为一些语言(我希望在IL级别上正确地实现这一点,而不仅仅是基于某个特定编译器的工作)可以让调用方承担确定要使用的默认值的责任,如果是这样,则需要一个
default(parameter.ParameterType)

这就是它变得更有趣的地方,因为如果没有默认值,
DefaultValue
显然是
DBNull.Value
(根据)。如果参数的类型为
object
IsOptional==true
,则这是不好的

在做了更多的挖掘之后,我希望解决这个问题的可靠方法是物理读取
ParameterInfo.Attributes
成员,首先读取位标志,以检查
ParameterAttributes.Optional
,然后检查
ParameterAttributes.Default
。只有当两者都存在时,读取
ParameterInfo.DefaultValue
才是正确的


我将围绕这一点开始编码和编写测试,但我希望有人拥有更多的IL知识,能够证实我的怀疑,并希望确认这对任何基于IL的语言都是正确的(从而避免需要用不同语言模拟库的负载!).

我的问题的简短答案是否定的-仅仅因为
isonational
为真并不意味着
DefaultValue
将实际包含默认值。我在问题文本中进一步的假设是正确的(而.Net文档确实以一种迂回的方式解释了这一点)。本质上,如果存在默认值,那么调用者应该使用它,否则调用者应该提供自己的默认值。参数的
属性
用于确定是否存在默认值

这就是我所做的

假设存在以下方法:

/* wrapper around a generic FastDefault<T>() that returns default(T) */
public object FastDefault(Type t) { /*elided*/ }
也就是说,除了可选的参数外,还提供了一个默认值(程序正确地落入到达
ParameterInfo.DefaultValue
)的分支中)

然后,在回答我问题的另一部分时,我意识到在C#4中,我们可以使用生成一个没有默认值的可选参数:

同样,程序正确地落入执行
FastDefault
方法的分支

(在本例中,C#也将使用类型的默认值作为此参数的参数)


我认为这涵盖了所有方面——它在我尝试过的所有方面都工作得很好(我很高兴尝试让过载分辨率感觉正确,因为我的IOC系统总是使用与命名参数等效的参数——但C#4规范在这方面有所帮助)。

正如你所说的,这是一种差异,并不可靠。.NET 4.5有,它检查一个参数是否是可选的(
IsOptional
),以及是否有一个默认值(
DefaultValue
)-与

(p.Attributes & ParameterAttributes.HasDefault) == ParameterAttributes.HasDefault
在早期版本中。这应该是正确的方法。另一种方法是替换无效的默认值,具体取决于在这种情况下无效值是什么(参数不是可选的,参数是可选的但没有默认值)。例如,你可以做:

if(p.DefaultValue != DBNull.Value)
{
    if(p.DefaultValue != Type.Missing)
        return p.DefaultValue;  //use the supplied default
    else
        return FastDefault(p.ParameterType); //use the FastDefault method
}
else  //parameter requires an argument - throw an exception
    throw new InvalidOperationException("Parameter requires an argument");
这是因为
p.DefaultValue
DBNull
,当参数不是可选的且
类型时。当可选参数但未提供默认值时,缺少


由于这是无文件记录的,我不建议这样做。最好是替换
p.DefaultValue!=DBNull.Value
带有
p.IsOptional
。最好是替换
p.DefaultValue!=键入.Missing
和您已回答的内容:
(p.Attributes¶metertattributes.HasDefault)=parametertattributes.HasDefault

我将测试
测试2
一个!并非所有语言都要求在声明可选参数时在函数签名中指定默认值。我不是专家,但在F#的情况下,默认值似乎应用于函数体中——其好处是,您可以在部署新版本的程序集时保持默认值的一致性(使用C#和VB模型时,今天可以使用不同的默认值编译库,但根据昨天的版本编译的程序集仍将发送旧的默认值)。F#文档是……因此,F#示例为我们提供了一个很好的模式,说明为什么要使用
[可选]
pattern而不是C#中的
int p1=0
:可能是因为您总是希望默认值是该类型的默认值,然后您将在函数体中应用另一个默认值。顺便说一句,
hasdaultvalue
在参数类型为
System.DateTime
时崩溃。这似乎是NET实现
public class Test
{
  public readonly string Message;
  public Test(string message = "hello") { Message = message; }
}
public class Test2
{
  public readonly string Message;
  public Test2([OptionalAttribute]string message) { Message = message; }
}
(p.Attributes & ParameterAttributes.HasDefault) == ParameterAttributes.HasDefault
if(p.DefaultValue != DBNull.Value)
{
    if(p.DefaultValue != Type.Missing)
        return p.DefaultValue;  //use the supplied default
    else
        return FastDefault(p.ParameterType); //use the FastDefault method
}
else  //parameter requires an argument - throw an exception
    throw new InvalidOperationException("Parameter requires an argument");