Параметры по умолчанию и отражение: если 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