我正在研究如何定义ParameterInfo.IsOptional
(我正在为内部IOC框架添加默认参数支持),在我看来,如果为true,则无法保证{{1} (或者确实ParameterInfo.DefaultValue
)实际上是要应用的默认值。
如果查看MSDN example given for IsOptional
,IL中似乎可以定义一个可选的参数,但是没有提供默认值(假设必须显式提供ParameterInfo.RawDefaultValue
)。即可能导致参数类型的情况,例如ParameterAttributes.HasDefault
,Int32
为真,但ParameterInfo.IsOptional
为空。
我的语言是C#,因此我可以处理编译器将要执行的操作。基于此我可以进行如下简单测试(ParameterInfo.DefaultValue
这里是parameter
实例,该方法用于返回一个实例,用作参数的运行时参数):
ParameterInfo
但是我认为某些语言(我希望在IL级别上做到这一点,而不仅仅是基于一个特定的编译器所做的)可以将责任放在调用者上确定要使用的默认值,如果是,则if(no_config_value)
{
if(!parameter.IsOptional) throw new InvalidOperationException();
//it's optional, so read the Default
return parameter.DefaultValue;
}
else
return current_method_for_getting_value();
需要按顺序排列。
这是更有趣的地方,因为default(parameter.ParameterType)
显然是DefaultValue
(根据the documentation for RawValue
),如果没有默认值。如果参数的类型为DBNull.Value
和object
!
进行了更多的挖掘后,我希望解决此问题的可靠方法是物理读取IsOptional==true
成员,单独读取位标志优先以检查{{ 1}}和然后检查ParameterInfo.Attributes
。只有 两者 存在时,才能正确阅读ParameterAttributes.Optional
。
我将开始编写代码并编写测试,但我希望有一个有更多IL知识的人可以证实我的怀疑,并希望确认这对任何基于IL的都是正确的语言(因此避免了用不同语言模拟大量库的需要!)。
答案 0 :(得分:6)
对我的问题的简短回答是否定 - 只是因为IsOptional
为真并不意味着DefaultValue
实际上会包含真正的默认值。我在问题文本中更进一步的假设是正确的(.Net文档确实有点解释这个问题,以一种全面的方式)。实质上,如果存在默认值,则调用者应该使用它,否则调用者应该提供它自己的默认值。参数的Attributes
用于确定是否存在默认值。
这就是我所做的。
假设存在以下方法:
/* wrapper around a generic FastDefault<T>() that returns default(T) */
public object FastDefault(Type t) { /*elided*/ }
然后给出一个特定的参数和提供的参数值的Dictionary(来自配置):
public object GetParameterValue(ParameterInfo p, IDictionary<string, object> args)
{
/* null checks on p and args elided - args can be empty though */
object argValue = null;
if(args.TryGetValue(p.Name, out argValue))
return argValue;
else if(p.IsOptional)
{
//now check to see if a default is supplied in the IL with the method
if((p.Attributes & ParameterAttributes.HasDefault) ==
ParameterAttributes.HasDefault)
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");
}
然后我在构造函数和方法上测试了这个逻辑:
public class Test
{
public readonly string Message;
public Test(string message = "hello") { Message = message; }
}
I.E,除了参数是可选的之外还提供默认值(程序正确地落入到达ParameterInfo.DefaultValue
的分支中)。
然后,在回答我问题的另一部分时,我意识到在C#4中我们可以使用OptionalAttribute
生成一个没有默认的可选参数:
public class Test2
{
public readonly string Message;
public Test2([OptionalAttribute]string message) { Message = message; }
}
同样,程序正确地落入执行FastDefault
方法的分支。
(在这种情况下,C#也将使用类型的默认值作为此参数的参数)
我认为这涵盖了所有内容 - 它在我尝试的所有内容上都很好用(我很乐意尝试让重载决策感觉正确,因为我的IOC系统总是使用相当于命名的参数 - 但C#4规范帮助了那里)。
答案 1 :(得分:0)
如你所说,存在差异并且不可靠。那么,.NET 4.5有HasDefaultValue,它检查参数是否是可选的(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
,而可选参数为Type.Missing
但未提供默认值。
由于这是没有记录的,我不推荐它。最好用p.DefaultValue != DBNull.Value
替换p.IsOptional
。更好的方法是将p.DefaultValue != Type.Missing
替换为您已经回答的内容:(p.Attributes & ParameterAttributes.HasDefault) == ParameterAttributes.HasDefault