???? Что такое Nullable reference type?

Nullable reference type явным образом указывает, должна ли переменная содержать значение или может отсутсвовать.

Ключевые особенности

  • Design time анализ

  • Четкие контракты API

  • Поиск потенциальных мест NullReferenceException с помощью подсказок IDE

???? Рекомендуемые требования

  • .NET Standard 2.1+

  • Unity 2021+

???? Как включить статический анализ

Для анализа отдельных сборок


Создаем файл csc.rsp рядом со сборкой asmdef :

В содержимое csc.rsp добавляем аргумент nullable :

-nullable:enable

Для анализа отдельных .cs файлов


В содержимое .cs добавляем контекст аннотаций #nullable :

#nullable enable

namespace Sandbox.Domain
{
    public struct User
    {
        public int Id { get; set; }
        public string Nickname { get; set; }
        public string? Phone { get; set; }
    }
}

Теперь использую Nullable тип, мы явно указываем что свойство Phone может отсутствовать.

???? Руководство по миграции

Что делать с биндами Unity UI и DI Inject атрибутами?

Следует явно сообщить анализатору, что мы сами гаранитруем присвоение, подавив предупреждения c помощью null-forgiving оператора !.

Пример с Unity UI:

[SerializeField] private Image _image = null!;

Пример с DI VContainer:

namespace Sandbox.Domain
{
    private MoveController _moveController = null!;

    [Inject]
    public void Init(MoveController moveController)
    {
        _moveController = moveController;
    }
}

Чтобы исключить из анализа отдельную часть кода, можно воспользоваться следующей аннотацией:

#nullable disable

using System;

namespace Sandbox.Server.Responses
{
    [Serializable]
    public class UserResponse
    {
        public int Id;
        public string Name;
    }
}

???? Полезные ссылки

Nullable reference types in Unity
NullReferenceException = ♥ 
Microsoft guide

Комментарии (5)


  1. rmolotov
    12.01.2024 18:01

    Сразу вопрос: зачем нам nullable-типы в проекте, что мы хотим получить? В статье мотивация не раскрыта, обозначу сам -- чтобы получать предупреждение в редакторе о потенциальных NRE в коде.

    Для этого существует Rider и аннотации, в частности [CanBeNull]. А здесь автор предлагает каждый класс пачкать директивой, так еще и по всему коду сборки бегать расставлять обратный костыль-директиву, в случае сборок разумеется.

    Отдельно отмечу что единственный профит от сборок (AssemblyDefenition) - ускорение перекомпиляции скриптов проекта. Когда их добавляют на геймплейные модули и системы - больше проблем от распутывания референсов, чем выигрыша от реюза кода. Все примеры обожания асм-дефов которые я наблюдал начинались из-за неадекватно большого желания сэкономить время (= жадности) и/или большой глупости и заканчивались плачевно для проекта. Проекты были ГК и матч3, с попыткой выделения SDK и "собственного" фреймворка, чтоб конвеерно шлёпать прототипы. Конец немного предсказуем. В больших и хардкорных проектах такого недуга не наблюдал. Поэтому asmdef-ы использую только для того кода, который никогда не меняется -- сторонние библиотеки и фреймворки (если еще не) и базовые UI-компоненты (окна/кнопки для префабов, кладу в отдельную папку и сборку).

    Отдельно поворчу, что:
    1. Неизменяемые данные лучше делать struct или хотя бы record. Судя по [Serializable] ,UserResponse - это все таки данные, одноразовые, зачем они class?
    2. Их открытые члены -- свойствами, а не полями. Есть еще атрибут [field:], если вдруг для Инспектора захочется не городить свойства+поля.
    3. Пул также совершенно зря наследуется от MonoBehaviour. Монобехи это бридж между C#-классами сервисов и миром Unity - объектами сцены. Не могу представить какие ссылки со сцены нужны пулу, ну если он только не спавнер одновременно.


    1. dreamcodestudio Автор
      12.01.2024 18:01

      Cпс, за комменты

      по поводу мотивиции, как раз указанн в самом вверху статьи 'Ключевые особенности' -

      • Поиск потенциальных мест с NullReference


      c 1-3 согласен, но данной контекс лишь изолированный пример, он служит только для показа nullable аннотаций


    1. dreamcodestudio Автор
      12.01.2024 18:01

      по поводу обмазывания аннотация никто это делать не предлагает, это лишь примеры как включить статический анализ в конкретных местах,
      никто не запрещает сделать один глобальный конфиг nullable или все доменную область Game.asmdef отметить данной аннотацией

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

      в конретной практике скажу, что это удобно даже больше для UPM packages, где юзер получает внешнее API и ему явно указывается контракт
      Для меня это просто банально выглядиь читаемый, чем подстановки [CanBeNull] аттрибуты, особенно в случае с сигнатурами.

      Это стоит воспринимать как еще один помощник статическу анализаторому, а не панацею от всех бед, т.к даже в офф. гите MS, можно найти довольно уникальный кейсы когда анализатор даже не заметит NullRef, т.к с новыми версиями .Net они дорабатывают ее.


  1. dreamcodestudio Автор
    12.01.2024 18:01

    конкретно по 2 пункту, field-property
    Стандартный Unity JsonUtility не умел сериализовать свойства и при вызове метода JsonUtility.ToJson результатом был пустой json.
    Отпишите, если вдруг в какой-то версиях это добавили,
    данный формат в Unity поддерживала только зависимость на Newtonsoft.Json


  1. dreamcodestudio Автор
    12.01.2024 18:01

    в случае использования JsonUtility через свойства с [field: SerializeField],
    получится json c backing fields вида:

    {<Id>"k__BackingField":30518,<Name>"k__BackingField":"mesh"}

    что явно может ломать backend, если на нем не используется явнай парсер от Unity

    Newtonsoft.Json же при сериализации property, имеет стандартный формат вида:

    {"Id":30518,"Name":"mesh"}