[.NET, C#] Ленивая инициализация в C#
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Отложенная инициализация или «ленивая» инициализация — это способ доступа к объекту, скрывающий за собой механизм, позволяющий отложить создание этого объекта до момента первого обращения. Необходимость ленивой инициализации может возникнуть по разным причинам: начиная от желания снизить нагрузку при старте приложения и заканчивая оптимизацией редко используемого функционала. И действительно, не все функции приложения используются всегда и, тем более, сразу, потому создание объектов, реализующих их, вполне рационально отложить до лучших времён. Я хотел бы рассмотреть варианты ленивой инициализации, доступные в языке 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
===========
Похожие новости:
- [Конференции] Новая неделя YouTube-стримов: от Vue.js до SIMD-инструкций
- [.NET, C#] Aspect-Oriented Programming (AOP) by source-level weaving
- [.NET, C#, Программирование, Проектирование и рефакторинг, Тестирование IT-систем] Мне было стыдно за свой интерпрайз-код настолько, что я сделал свой велосипед. За него стыдно меньше
- [.NET, C#, Программирование, Проектирование и рефакторинг] Архитектура интерпрайз-приложений может быть другой
- [.NET, C#] Aspect Oriented Programming (AOP) через исходный код
- [.NET, C#] Интеграция с “Госуслугами”. Применение Workflow Core (часть II)
- [Информационная безопасность, Совершенный код, Управление продуктом, Софт] Строим безопасную разработку в ритейлере. Опыт одного большого проекта
- [.NET, Программирование] Принятого не воротай: Enumerable vs List
- [.NET, C#] Немного о велосипедах
- [C#] Бегущая строка на C#
Теги для поиска: #_.net, #_c#, #_c#, #_lazy_initialization, #_lenivaja_initsializatsija (ленивая инициализация), #_c#_8, #_.net, #_novichkam (новичкам), #_.net, #_c#
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 02:17
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Отложенная инициализация или «ленивая» инициализация — это способ доступа к объекту, скрывающий за собой механизм, позволяющий отложить создание этого объекта до момента первого обращения. Необходимость ленивой инициализации может возникнуть по разным причинам: начиная от желания снизить нагрузку при старте приложения и заканчивая оптимизацией редко используемого функционала. И действительно, не все функции приложения используются всегда и, тем более, сразу, потому создание объектов, реализующих их, вполне рационально отложить до лучших времён. Я хотел бы рассмотреть варианты ленивой инициализации, доступные в языке 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 =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 02:17
Часовой пояс: UTC + 5