Нулевая проверка нескольких параметров и создание исключения с их именем

Я хотел бы проверить несколько параметров и выдать ArgumentNullException, если какой-либо из них равен null. Ради аргумента предположим, что у меня есть это:

public void DoSomething(SomeClass param1, SomeClass param2, SomeClass param3);

Конечно, я мог бы сделать:

if (param1 == null)
    throw new ArgumentNullException(nameof(param1));
if (param2 == null)
    throw new ArgumentNullException(nameof(param2));
if (param3 == null)
    throw new ArgumentNullException(nameof(param3));

Но это не особенно красиво, особенно если это повторяющаяся проверка по всему приложению. Итак, я думал, что я сделаю это:

public static class ValidationExtensions
{
    public static void NullCheck<T>(this T subject)
    {
        if (T == null)
            throw new ArgumentNullException();
    }
}

// ...

param1.NullCheck();
param2.NullCheck();
param3.NullCheck();

Но так я теряю nameof. Я не могу сделать nameof(subject), так как это бессмысленно.

Конечно, это вариант:

public static class ValidationExtensions
{
    public static void NullCheck<T>(this T subject, string parameterName)
    {
        if (T == null)
            throw new ArgumentNullException(parameterName);
    }
}

// ...

param1.NullCheck(nameof(param1));
param2.NullCheck(nameof(param2));
param3.NullCheck(nameof(param3));

Но, кажется, он склонен к ошибкам, с повторяющимися параметрами... и, честно говоря, просто некрасиво.

Есть ли хороший способ сделать это? В идеале без использования каких-либо внешних библиотек.


person user622505    schedule 06.03.2019    source источник
comment
param1.NullCheck(nameof(param1)); кажется не таким уж плохим - он менее подвержен ошибкам, чем ваш первый вариант. Что подвержено ошибкам, так это просто создание исключений для null. Наверное, лучше попробовать код вообще без исключений. Прочитайте это: Досадные исключения Эрика Липперта.   -  person Enigmativity    schedule 06.03.2019
comment
@Enigmativity, это определенно хорошее чтение, но это больше о том, чтобы перехватывать исключения, а не генерировать их. Мой текущий вариант использования в основном связан с предотвращением отсутствия вещей в конфигурациях - я бы предпочел выбросить раньше и позволить потребителю моего пакета точно знать, что им не хватает, а не игнорировать это и менее предсказуемо ошибиться на более позднем этапе. param1.NullCheck(nameof(param1)); определенно не конец света, но просто кажется, что должен быть способ сделать это таким образом, чтобы ссылаться на param1 только один раз...   -  person user622505    schedule 06.03.2019
comment
Он говорит такие вещи, как «Старайтесь никогда не писать библиотеку самостоятельно, которая выдает раздражающее исключение», и не слишком сложно экстраполировать, как писать хороший код, который не вызывает исключений. И я не знаю, почему вы говорите, а не игнорируете это и менее предсказуемо терпите неудачу на более позднем этапе - там ничего нет об игнорировании ошибок. В вашем случае сложно дать дальнейший совет, поскольку ваш код не показывает никаких подробностей в методе.   -  person Enigmativity    schedule 07.03.2019


Ответы (2)


Наиболее кратким и поддерживаемым решением будет то, что у вас есть, или C#7 Выражение Throw

param1 = param1 ?? throw new ArgumentNullException(nameof(param1));

Вы можете использовать Выражения и некоторые хитрости, хотя я бы не рекомендовал это, это пахнет и скрывает простую логику за абстракцией< /strong> и накладные расходы. Кроме того, он основан на неопределенном поведении, которое может измениться в будущем.

Однако, помимо всего этого, я даю вам Выражения

public static class Validator
{
   public static void Validate<T>(Expression<Func<string, T>> f)
   {
      var name = (f.Body as MemberExpression).Member.Name;
      if(f.Compile().Invoke(name) == null)
         throw new ArgumentNullException(name);    
   }
}

Причина, по которой это работает, заключается в том, что компилятор создает класс для лямбда-выражения (замыкание), а локальная переменная становится свойством Member.Name, что означает, что она также должна работать и для свойств (не проверено)

Использование

public static void Test(string param1, string param2)
{
   Validator.Validate(x => param1);
}

public static void Main()
{
   Test(null,"asdf");
}

Вывод

Значение не может быть нулевым. Имя параметра: param1

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

person TheGeneral    schedule 06.03.2019
comment
Я согласен с вами, это не похоже на путь. Спасибо за предоставление рабочего решения, даже если я не буду его использовать;) Я подожду некоторое время, прежде чем принять его, на тот случай, если кто-то другой ответит лучше. - person user622505; 06.03.2019
comment
еще лучший ответ * ;) - person user622505; 06.03.2019

Остерегайтесь этих «оптимизаций кода». Деревья отражений и выражений имеют снижение производительности.

Даже ваш вариант с дженериками приводит к тому, что разработчик использует его без необходимости. По крайней мере, добавьте к нему ограничение:

public static void NullCheck<T>(this T subject, string parameterName) where T : class

Конечно, это тоже не бесплатно.

С появлением throw как выражение в C# 7.0 (как Michael Randall показано в его ответе), это одна строка, и Visual Studio сделает это за вас.

person Paulo Morgado    schedule 06.03.2019