Как избавиться от строковых констант при проверке аргументов на null, то есть избавиться от хард-кодед констант. Имеем пример:
private static void FillStringBuilderCommon(StringBuilder stringBuilder, String stringArgument)
{
if (stringBuilder == null)
{
throw new ArgumentNullException("stringBuilder"); }
if (stringArgument == null)
{
throw new ArgumentNullException("stringArgument");
}
stringBuilder.Append(stringArgument);
}
* This source code was highlighted with Source Code Highlighter.
Выделенное было давно не по душе, и я искал пути, как бы сделать прямое вытягивание имен аргументов. Первоначальную идею задал топик на rsdn про удобное получение имени свойства. В результате чего было получен следующий Extension method.
static class Extensions
{
public static string GetName<TResult>(this Expression<Func<TResult>> expression)
{
return ((MemberExpression)expression.Body).Member.Name;
}
}
* This source code was highlighted with Source Code Highlighter.Что привело к следующему варианту функции проверки аргумента на null
public static void NullValidate1<TResult>(Expression<Func<TResult>> argumentExpresion)
where TResult : class
{
if (argumentExpresion.Compile()() == null)
{
throw new ArgumentNullException(argumentExpresion.GetName());
}
}
* This source code was highlighted with Source Code Highlighter.
Жутко медленная вещь получилась, я вам скажу, что естественно меня не уcтраивало ни коим образом. Данная функция позволяет, кратко и понятно записать вызов валидации, например:
Tools.NullValidate1(() => stringBuilder);
* This source code was highlighted with Source Code Highlighter.
Красиво, компактно, но, повторюсь, жутко медленно, а целью было — создание высокопроизводительного инструмента. Дальнейшее изучение данной темы привело к выводу, что одним экспрешеном не обойтись, и функционал проверки аргумента придется делать отдельно и к нему прикреплять собственно сам экспрешен. В результате получился следующий вариант:
[DebuggerStepThrough]
public static void NullValidate2<TResult>(Expression<Func<TResult>> argumentExpresion, TResult argument)
where TResult : class
{
if (argument == null)
{
throw new ArgumentNullException(argumentExpresion.GetName());
}
}
* This source code was highlighted with Source Code Highlighter.
Получилось лучше. Стало гораздо быстрее работать, но, забегая вперед, недостаточно быстро. Вызов же стал немногим сложнее. На один аргумент.
Tools.NullValidate1(() => stringBuilder, stringBuilder);
* This source code was highlighted with Source Code Highlighter.
Как показали тесты на произволительность, потери были значительными, на порядки. Вся проблема заключалась в первом аргументе. Объяснять «почему?» думаю не надо.
В итоге пришел к заключительному виду, коим пользуюсь сейчас:
[DebuggerStepThrough]
public static void NullValidate<TResult>(TResult argument, Func<Expression<Func<TResult>>> nameRetreiver)
where TResult : class
{
if (argument == null)
{
throw new ArgumentNullException(nameRetreiver().GetName());
}
}
* This source code was highlighted with Source Code Highlighter.
Вызывается так:
Tools.NullValidate(stringBuilder, () => (() => stringBuilder));
Таким образом тестовая функция в целом выглядит следующим образом:
private static void FillStringBuilderExpression(StringBuilder stringBuilder, String stringArgument)
{
Tools.NullValidate(stringArgument, () => (() => stringArgument));
Tools.NullValidate(stringBuilder, () => (() => stringBuilder));
stringBuilder.Append(stringArgument);
}
* This source code was highlighted with Source Code Highlighter.
Результаты перформанс теста:
[FillStringBuilderCommon] StringBuilder length: 3000000
[FillStringBuilderCommon]: 00:00:00.0532751
[FillStringBuilderExpression2] StringBuilder length: 3000000
[FillStringBuilderExpression2]: 00:00:07.2346404
[FillStringBuilderExpression] StringBuilder length: 3000000
[FillStringBuilderExpression]: 00:00:00.1308366
Press any key to exit...
Код теста:
static void Main(string[] args)
{
MeasureFunc((sb, arg) => FillStringBuilderCommon(sb, arg));
MeasureFunc((sb, arg) => FillStringBuilderExpression2(sb, arg));
MeasureFunc((sb, arg) => FillStringBuilderExpression(sb, arg));
Console.WriteLine("Press any key to exit...");
Console.Read();
}
private static void MeasureFunc(Expression<Action<StringBuilder, string>> functionExpression)
{
const uint limit = 1000000;
string title = string.Format("[{0}]", ((MethodCallExpression)functionExpression.Body).Method.Name);
Action<StringBuilder, string> function = functionExpression.Compile();
StringBuilder stringBuilder = new StringBuilder();
String stringArgument = "saa";
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
uint index = 0;
while (index++ < limit)
{
function(stringBuilder, stringArgument);
}
stopwatch.Stop();
Console.WriteLine(title + " StringBuilder length: " + stringBuilder.Length);
Console.WriteLine(title + ": " + stopwatch.Elapsed);
}
* This source code was highlighted with Source Code Highlighter.
С уважением, Александр.