默认参数和反射:如果ParameterInfo.IsOptional那么DefaultValue总是可靠的吗?

时间:2012-04-02 13:38:01

标签: .net reflection compiler-construction

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

如果查看MSDN example given for IsOptional,IL中似乎可以定义一个可选的参数,但是没有提供默认值(假设必须显式提供ParameterInfo.RawDefaultValue)。即可能导致参数类型的情况,例如ParameterAttributes.HasDefaultInt32为真,但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.Valueobject

,那么这是不好的

进行了更多的挖掘后,我希望解决此问题的可靠方法是物理读取IsOptional==true成员,单独读取位标志优先以检查{{ 1}}和然后检查ParameterInfo.Attributes。只有 两者 存在时,才能正确阅读ParameterAttributes.Optional

我将开始编写代码并编写测试,但我希望有一个有更多IL知识的人可以证实我的怀疑,并希望确认这对任何基于IL的都是正确的语言(因此避免了用不同语言模拟大量库的需要!)。

2 个答案:

答案 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.DefaultValueDBNull,而可选参数为Type.Missing但未提供默认值。

由于这是没有记录的,我不推荐它。最好用p.DefaultValue != DBNull.Value替换p.IsOptional。更好的方法是将p.DefaultValue != Type.Missing替换为您已经回答的内容:(p.Attributes & ParameterAttributes.HasDefault) == ParameterAttributes.HasDefault