Русский
Русский
English
Статистика
Реклама

Ленивая инициализация в C

Отложенная инициализация или ленивая инициализация это способ доступа к объекту, скрывающий за собой механизм, позволяющий отложить создание этого объекта до момента первого обращения. Необходимость ленивой инициализации может возникнуть по разным причинам: начиная от желания снизить нагрузку при старте приложения и заканчивая оптимизацией редко используемого функционала. И действительно, не все функции приложения используются всегда и, тем более, сразу, потому создание объектов, реализующих их, вполне рационально отложить до лучших времён. Я хотел бы рассмотреть варианты ленивой инициализации, доступные в языке C#.

Для демонстрации примеров я буду использовать класс Test, у которого есть свойство BlobData, возвращающее объект типа Blob, который по легенде создаётся довольно медленно, и было решено создавать его лениво.

class Test{    public Blob BlobData    {        get        {            return new Blob();        }    }}


Проверка на null


Самый простой вариант, доступный с первых версий языка, это создание неинициализированной переменной и проверка её на null перед возвращением. Если переменная равна null, создаём объект и присваиваем этой переменной, а потом его возвращаем. При повторном обращении объект уже будет создан и мы сразу его вернём.

class Test{    private Blob _blob = null;    public Blob BlobData    {        get        {            if (_blob == null)            {                _blob = new Blob();            }            return _blob;        }    }}


Объект типа Blob тут создаётся при первом обращении к свойству. Либо не создаётся, если он по какой-то причине в этой сессии программе не понадобился.

Тернарный оператор ?:


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

class Test{    private Blob _blob = null;    public Blob BlobData    {        get        {            return _blob == null                ? _blob = new Blob()                : _blob;        }    }}


Суть осталась той же. Если объект не инициализирован, инициализируем и возвращаем. Ежели уже инициализирован, то просто сразу вовзращаем.

is null


Ситуации бывают разные и мы, например, можем столкнуться с такой, в которой у класса Blob перегружен оператор ==. Для этого, вероятно, нам может потребоваться сделать проверку is null вместо == null.

return _blob is null    ? _blob = new Blob()    : _blob;


Но это так, небольшое отступление.

Null-coalescing оператор ??


Ещё больше упростить код нам поможет бинарный оператор ??
Суть его работы такова. Если первый операнд не равен null, то он и возвращается. Если же первый операнд равен null, возвращается второй.

class Test{    private Blob _blob = null;    public Blob BlobData    {        get        {            return _blob ?? (_blob = new Blob());        }    }}


Второй операнд пришлось взять в круглые скобки из-за приоритета операций.

Оператор ??=


В C# 8 появился null-coalescing assignment operator, выглядящий вот так ??=
Принцип его работы заключается в следующем. Если первый операнд не равен null, то он просто возвращается. Если первый операнд равен null, то ему присваивается значение второго и возвращается уже это значение.

class Test{    private Blob _blob = null;    public Blob BlobData    {        get        {            return _blob ??= new Blob();        }    }}


Это позволило ещё немного сократить код.

Потоки


Если есть вероятность, что к данному ресурсу могут обращаться сразу несколько потоков, нам стоит сделать его потокобезопасным. В противном случае может случиться такая ситуация, что, например, оба потока проверят объект на null, результат окажется false, а затем будет создано два объекта типа Blob, нагрузив систему в два раза больше, чем нам хотелось, и кроме того, один из этих объектов сохранится, а второй будет потерян.

class Test{    private readonly object _lock = new object();    private Blob _blob = null;    public Blob BlobData    {        get        {            lock (_lock)            {                return _blob ?? (_blob = new Blob());            }        }    }}


Оператор lock получает взаимоисключающую блокировку заданного объекта перед выполнением определенных операторов, а затем снимает блокировку. Он является эквивалентом использования метода System.Threading.Monitor.Enter(..., ...);

Lazy<T>


В .NET 4.0 появился класс Lazy, позволяющий скрыть всю эту грязную работу от наших глаз. Теперь мы можем оставить только локальную переменную типа Lazy. При обращении к его свойству Value, мы получим объект класса Blob. Если объект был создан ранее, он сразу вернётся, если нет сначала будет создан.

class Test{    private readonly Lazy<Blob> _lazy = new Lazy<Blob>();    public Blob BlobData    {        get        {            return _lazy.Value;        }    }}


Так как у класса Blob есть конструктор без параметров, то Lazy сможет создать его в нужный момент без лишних вопросов. Если же нам нужно выполнить какие-то дополнительные действия во время создания объекта Blob, конструктор класса Lazy может принимать ссылку на Func<T>

private Lazy<Blob> _lazy = new Lazy<Blob>(() => new Blob());


Кроме того, во втором параметре конструктора мы можем указать, нужна ли нам потокобезопасность (тот самый lock).

Свойство


Теперь давайте сократим запись readonly свойства, благо современный C# позволяет это делать красиво. В конечном итоге выглядеть всё это станет так:

class Test{    private readonly Lazy<Blob> _lazy = new Lazy<Blob>();    public Blob BlobData => _lazy.Value;}


LazyInitializer


Ещё есть вариант не оборачивать класс в обёртку Lazy, а вместо этого использовать функционал LazyInitializer. Этот класс имеет один статический метод EnsureInitialized с кучей перегрузок, позволяющих творить всякое, в том числе делать потокобезопасность и писать кастомный код для создания объекта, но основная суть которого заключается в следующем. Проверить, не инициализирован ли объект. Если нет, то инициализировать. Вернуть объект. С использованием данного класса, мы можем переписать наш код так:

class Test{    private Blob _blob;    public Blob BlobData => LazyInitializer.EnsureInitialized(ref _blob);}


На этом всё. Спасибо за внимание.
Источник: habr.com
К списку статей
Опубликовано: 07.10.2020 04:07:15
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Net

C

Lazy initialization

Ленивая инициализация

C# 8

Новичкам

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru