Параметры по умолчанию и отражение: если ParameterInfo.IsOptional, то всегда ли DefaultValue надежно? - PullRequest
6 голосов
/ 02 апреля 2012

Я смотрю на то, как определяется ParameterInfo.IsOptional (я добавляю поддержку параметров по умолчанию во внутреннюю структуру IOC), и мне кажется, что при истинном значении нет гарантии, что 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 ]

6 голосов
/ 03 апреля 2012

Короткий ответ на мой вопрос - нет - просто потому, что 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; }
}

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 ).

0 голосов
/ 24 апреля 2013

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...