Параметры по умолчанию и отражение: если ParameterInfo.IsOptional, то всегда ли DefaultValue надежно?

Я смотрю как ParameterInfo.IsOptional определяется (я добавляю поддержку параметров по умолчанию во внутреннюю структуру IOC), и мне кажется, что при значении true нет гарантии, что ParameterInfo.DefaultValue (или действительно ParameterInfo.RawDefaultValue) на самом деле значения по умолчанию, которые должны быть применены.

Если вы посмотрите на пример MSDN, приведенный дляIsOptionalв IL возможно определить параметр, который является необязательным, но для которого не задано значение по умолчанию (учитывая, что ParameterAttributes.HasDefault должно быть явно указано). Т.е. потенциально может привести к ситуации, когда тип параметра, скажем, Int32, ParameterInfo.IsOptional верно, но ParameterInfo.DefaultValue нулевой.

Мой язык - C#, поэтому я могу работать над тем, что будет делатьэтот компилятор. Исходя из этого, я могу провести простой тест следующим образом (parameterвотParameterInfoэкземпляр, и метод предназначен для возврата экземпляра, который будет использоваться в качестве аргумента времени выполнения для параметра):

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();

Но я думаю, что некоторые языки (и я хочу сделать это правильно на уровне IL, а не просто на основе того, что делает один конкретный компилятор) могут возложить бремя на вызывающую функцию, чтобы определить значение по умолчанию, которое будет использоваться, если итакdefault(parameter.ParameterType)должно быть в порядке.

Здесь становится немного интереснее, потому что DefaultValueесть, по-видимому DBNull.Value(согласно документации для RawValue) если нет по умолчанию. Что не хорошо, если параметр имеет типobjectа такжеIsOptional==true!

Надеюсь, что проделав еще больше рытья, надежный способ решить эту проблему - это физически прочитатьParameterInfo.Attributes член, читая битовые флаги индивидуально, чтобы проверить ParameterAttributes.Optional а затем проверьте ParameterAttributes.Default, Только если оба присутствуют, то чтение ParameterInfo.DefaultValue будет правильно.

Я собираюсь начать кодирование и написание тестов вокруг этого, но я спрашиваю в надежде, что есть кто-то с большим знанием IL, который может подтвердить мои подозрения и надеюсь, что это будет правильным для любого языка на основе IL (таким образом, избегая необходимости макетировать множество библиотек на разных языках!).

2 ответа

Решение

Короткий ответ на мой вопрос - нет, просто потому, что IsOptional правда не значит, что DefaultValue на самом деле будет содержать реальное значение по умолчанию. Мои предположения, приведенные ниже в тексте вопроса, были правильными (и документация.Net действительно объясняет это в общих чертах). По сути, если существует значение по умолчанию, то вызывающий должен использовать его, в противном случае вызывающий должен указать свой собственный параметр по умолчанию. Параметр Attributes используются, чтобы выяснить, существует ли значение по умолчанию.

Это то, что я сделал.

Предположим, существует следующий метод:

/* wrapper around a generic FastDefault<T>() that returns default(T) */
public object FastDefault(Type t) { /*elided*/ }

А затем задается конкретный параметр и словарь предоставленных значений аргумента (из конфигурации):

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; }
}

IE, где по умолчанию предоставляется в дополнение к необязательному параметру (программа правильно попадает в ветку, которая достигает ParameterInfo.DefaultValue).

Затем, отвечая на другую часть моего вопроса, я понял, что в C# 4 мы можем использовать OptionalAttribute создать необязательный параметр без значения по умолчанию:

public class Test2
{
  public readonly string Message;
  public Test2([OptionalAttribute]string message) { Message = message; }
}

Опять же, программа правильно попадает в ветку, которая выполняет FastDefault метод.

(В этом случае C# также будет использовать тип по умолчанию в качестве аргумента для этого параметра)

Я думаю, что это покрывает все - это работает хорошо на всем, что я пробовал (мне было весело пытаться получить правильное разрешение перегрузки, так как моя система IOC всегда использует эквивалент именованных аргументов - но там помогала спецификация C# 4).

Как вы заявили, разница есть и она ненадежна. Ну, в.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

Другие вопросы по тегам