Как стать автором
Обновить

C#. Argument null validator

Как избавиться от строковых констант при проверке аргументов на 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.

С уважением, Александр.
Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.