Argument null validator
От: Uzzy Россия  
Дата: 26.11.09 14:05
Оценка: 113 (3) :)
Доброго времени суток.

Данный пост навеян этим сообщением и последующим обсуждением: http://rsdn.ru/forum/dotnet/3590447.1.aspx
Автор: Uzzy
Дата: 03.11.09


Там приводился сырой вариант, как избавится от строковых констант, при проверке аргументов на 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);
        }


Выделенное было давно не по душе, и искал пути, как бы сделать прямое вытягивание имен аргументов. Первоначальную идею задал топик про удобное получение имени свойства. В результате чего было получен следующий Extension method.

    static class Extensions
    {
        public static string GetName<TResult>(this Expression<Func<TResult>> expression)
        {
            return ((MemberExpression)expression.Body).Member.Name;
        }
    }


Что привело к следующему варианту функции проверки аргумента на null

        public static void NullValidate1<TResult>(Expression<Func<TResult>> argumentExpresion)
            where TResult : class
        {
            if (argumentExpresion.Compile()() == null)
            {
                throw new ArgumentNullException(argumentExpresion.GetName());
            }
        }


Жутко медленная вещь получилась, я вам скажу, что естественно меня не утраивало ни коим образом. (на этом месте заканчивается выше указанное обсуждение). Что данная функция позвляет, кратко и понятно записать вызов валидации, например:

            Tools.NullValidate1(() => stringBuilder);


Красиво, компактно, но, повторюсь, жутко медленно, а целью было — создание высокопроизводительного инструмента. Дальнейшее изучение данной темы привело к выводу, что одним экспрешеном не обойтись, и функционал проверки аргумента придется делать отдельно и к нему прикреплять собственно сам экспрешшен. В результате получился, следующий вариант:

        [DebuggerStepThrough]
        public static void NullValidate2<TResult>(Expression<Func<TResult>> argumentExpresion, TResult argument)
            where TResult : class
        {
            if (argument == null)
            {
                throw new ArgumentNullException(argumentExpresion.GetName());
            }
        }


Получилось лучше. Стало гораздо быстрее работать, но, забегая вперед, недостаточно быстро. Вызов же стал немногим сложнее. На один аргумент.

            Tools.NullValidate1(() => stringBuilder, stringBuilder);


Как показали тесты на произволительность, потери были значительными, на порядок. Вся проблема заключалась в первом аргументе. Объяснять "почему?" думаю не надо.
В итоге пришел к заключительному виду, коим пользуюсь сейчас:

        [DebuggerStepThrough]
        public static void NullValidate<TResult>(TResult argument, Func<Expression<Func<TResult>>> nameRetreiver)
            where TResult : class
        {
            if (argument == null)
            {
                throw new ArgumentNullException(nameRetreiver().GetName());
            }
        }


Вызывается так:

            Tools.NullValidate(stringBuilder, () => (() => stringBuilder));


Таким образом тестовая функция в целом выглядит следующим образом:

        private static void FillStringBuilderExpression(StringBuilder stringBuilder, String stringArgument)
        {
            Tools.NullValidate(stringArgument, () => (() => stringArgument));
            Tools.NullValidate(stringBuilder, () => (() => stringBuilder));

            stringBuilder.Append(stringArgument);
        }


Результаты перформанс теста:

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


С уважением, Александр.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.