Отметить параметры как НЕ допускающие значения NULL в C # /. NET?

99

Есть ли простой атрибут или контракт данных, который я могу назначить параметру функции, который предотвращает передачу nullв C # /. NET? В идеале это также должно было бы проверяться во время компиляции, чтобы убедиться, что литерал nullнигде не используется для него и во время выполнения ArgumentNullException.

Сейчас я пишу что-то вроде ...

if (null == arg)
  throw new ArgumentNullException("arg");

... за каждый аргумент, которого я не ожидаю null.

В том же примечании, есть ли противоположность тому, Nullable<>что следующее не сработает:

NonNullable<string> s = null; // throw some kind of exception
Нил С. Обремски
источник
7
В последних версиях C # использование nameof () работает лучше: throw new ArgumentNullException (nameof (arg)); Таким образом, если вы реорганизуете имя, если в вашем операторе throw
появится
C # 8 теперь поддерживает ссылочные типы, допускающие значение NULL, что является параметром компилятора, который вы можете включить в своем проекте. docs.microsoft.com/en-us/dotnet/csharp/nullable-references
Пол Стеглер,

Ответы:

69

К сожалению, во время компиляции нет ничего доступного.

У меня есть хакерское решение, которое я недавно опубликовал в своем блоге, в котором используется новая структура и преобразования.

В .NET 4.0 с материалом Code Contracts жизнь станет намного лучше. Было бы неплохо иметь реальный синтаксис языка и поддержку недопустимости обнуления, но контракты кода очень помогут.

У меня также есть метод расширения в MiscUtil под названием ThrowIfNull, который делает его немного проще.

И последнее: есть ли причина для использования " if (null == arg)" вместо " if (arg == null)"? Я считаю, что последнее легче читать, а проблема, которую первый решает на C, не относится к C #.

Джон Скит
источник
2
> К сожалению, во время компиляции нет ничего доступного. > ...> Было бы неплохо иметь реальный синтаксис языка и поддержку недопустимости обнуления, я согласен. Я также хотел бы, чтобы возникали ошибки времени компиляции.
AndrewJacksonZA
2
@Jon, не работает ли if (arg = null), если есть неявное приведение к определенному типу bool? Я допускаю, что это может показаться извращенным, но оно действительно компилируется ...
Томас С. Триас
11
@ ThomasS.Trias: Да, в этом невероятно непонятном пограничном случае, в сочетании с опечаткой, в сочетании с отсутствием тестов для этого кода, вы бы столкнулись с проблемой. К этому моменту, я думаю, у вас есть более серьезные проблемы :)
Джон Скит
4
@Jon: Конечно. Думаю, я откажусь от своих любимых условий Йоды. :-)
Thomas S. Trias
1
@SebastianMach: Я не верю, что причина if (null == x)в различиях естественного языка. Я считаю, что это действительно устаревший стиль, который просто распространяется через примеры и т. Д.
Джон Скит
22

Я знаю, что невероятно опаздываю с этим вопросом, но я чувствую, что ответ станет актуальным, когда последняя крупная итерация C # приблизится к выпуску, а затем будет выпущена. В C # 8.0 произойдет серьезное изменение, C # будет предполагать, что все типы не считаются нулевыми.

По словам Мадса Торгерсена:

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

Кроме того, иногда значение null само по себе является разумным значением. Иногда вы хотите изобразить тот факт, что, скажем, поле не имеет значения. Что можно передать «ничего» в качестве параметра. Однако упор делается на иногда. И в этом заключается еще одна часть проблемы: языки, подобные C #, не позволяют вам выразить, является ли нуль здесь хорошей идеей или нет.

Итак, резолюция, изложенная Мэдсом, такова:

  1. Мы считаем, что чаще всего требуется, чтобы ссылка не была нулевой. Обнуляемые ссылочные типы будут более редкими (хотя у нас нет точных данных, чтобы сказать нам, в каком количестве), поэтому именно они требуют новой аннотации.

  2. В языке уже есть понятие и синтаксис для типов значений, допускающих значение NULL. Аналогия между ними сделает добавление языка концептуально проще, а лингвистически проще.

  3. Кажется правильным, что вы не должны обременять себя или своего потребителя громоздкими нулевыми значениями, если вы сами не решили, что они вам нужны. Нулевые значения, а не их отсутствие, должны быть тем, что вы должны явно выбрать.

Пример желаемой функции:

public class Person
{
     public string Name { get; set; } // Not Null
     public string? Address { get; set; } // May be Null
}

Предварительная версия доступна для Visual Studio 2017, предварительная версия версии 15.5.4+.

Грег
источник
1
Кто-нибудь знает, стало ли это когда-нибудь частью C # 8.0?
TroySteven
@TroySteven Да, вы должны выбрать его через настройки в Visual Studio.
Грег
@TroySteven Вот документация по нему. docs.microsoft.com/en-us/dotnet/csharp/nullable-references
Грег,
Хорошо, поэтому похоже, что вам нужно включить его, прежде чем он начнет работать, это хорошо для обратной совместимости.
TroySteven
15

Я знаю, что это ОЧЕНЬ старый вопрос, но здесь не было этого:

Если вы используете ReSharper / Rider, вы можете использовать аннотированный фреймворк .

Изменить : я получил случайный -1 для этого ответа. Это хорошо. Просто имейте в виду, что он по- прежнему действителен, хотя он больше не рекомендуется для проектов C # 8.0 + (чтобы понять, почему, см . Ответ Грега ).

Рсенна
источник
1
Интересно, что по умолчанию ваше скомпилированное приложение не будет иметь ссылки на JetBrains.Annotations.dll, поэтому вам не нужно распространять его вместе с приложением: Как использовать аннотации JetBrains для улучшения инспекций ReSharper
Lu55
9

Ознакомьтесь с валидаторами в корпоративной библиотеке. Вы можете сделать что-то вроде:

private MyType _someVariable = TenantType.None;
[NotNullValidator(MessageTemplate = "Some Variable can not be empty")]
public MyType SomeVariable {
    get {
        return _someVariable;
    }
    set {
        _someVariable = value;
    }
}

Затем в вашем коде, когда вы хотите его проверить:

Microsoft.Practices.EnterpriseLibrary.Validation.Validator myValidator = ValidationFactory.CreateValidator<MyClass>();

ValidationResults vrInfo = InternalValidator.Validate(myObject);
Не я
источник
0

не самый красивый, но:

public static bool ContainsNullParameters(object[] methodParams)
{
     return (from o in methodParams where o == null).Count() > 0;
}

вы также можете проявить больше творчества в методе ContainsNullParameters:

public static bool ContainsNullParameters(Dictionary<string, object> methodParams, out ArgumentNullException containsNullParameters)
       {
            var nullParams = from o in methodParams
                             where o.Value == null
                             select o;

            bool paramsNull = nullParams.Count() > 0;


            if (paramsNull)
            {
                StringBuilder sb = new StringBuilder();
                foreach (var param in nullParams)
                    sb.Append(param.Key + " is null. ");

                containsNullParameters = new ArgumentNullException(sb.ToString());
            }
            else
                containsNullParameters = null;

            return paramsNull;
        }

конечно, вы можете использовать перехватчик или отражение, но за ними легко следить / использовать с небольшими накладными расходами

Джефф Гризл
источник
3
Очень плохая практика использования таких запросов: return (from o in methodParams where o == null).Count() > 0; Использование: return methodParams.Any(o=>o==null);это будет намного быстрее при больших коллекциях
Яваноста
Зачем передавать исключение? Почему бы просто не выбросить?
Мартин Каподичи
-4

Хорошо, этот ответ немного запоздал, но вот как я его решаю:

public static string Default(this string x)
{
    return x ?? "";
}

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

Например

if (model.Day.Default() == "")
{
    //.. Do something to handle no Day ..
}

Не идеально, я знаю, так как вы должны помнить, что везде нужно вызывать default, но это одно из решений.

Мартин Каподичи
источник
5
Чем это лучше / проще, чем проверка (x == null)? или функция String.IsNotNullOrEmpty.
Batavia
Не намного лучше, чем на string.IsNotNullOrEmptyсамом деле, кроме того сахара, что то, что вам небезразлично слева. Может быть загружен в другие функции (длина, конкатенация) и т. Д. Немного лучше.
Мартин Каподичи
@MartinCapodici Кроме того, это создает иллюзию того, что вы можете безопасно вызывать метод экземпляра ( Default()) для null ( model.Day). Я знаю, что методы расширения не проверяются на нулевое значение, но мои глаза не знают, и они уже представили это ?в коде model?.Day?.Default():)
Альб,