Кэш-память vs Lazy <T>

В .NET 4 следующий фрагмент с кэшированным свойством также можно записать с помощью класса System.Lazy<T>. Я измерил производительность обоих подходов, и это почти то же самое. Есть ли какая-то реальная польза или магия для того, почему я должен использовать один над другим?

Кэшированное свойство

public static class Brushes
{
    private static LinearGradientBrush _myBrush;

    public static LinearGradientBrush MyBrush
    {
        get
        {
            if (_myBrush == null)
            {
                var linearGradientBrush = new LinearGradientBrush { ...};
                linearGradientBrush.GradientStops.Add( ... );
                linearGradientBrush.GradientStops.Add( ... );

                _myBrush = linearGradientBrush;
            }

            return _myBrush;
        }
    }
}

Ленивый < Т >

public static class Brushes
{
    private static readonly Lazy<LinearGradientBrush> _myBrush =
        new Lazy<LinearGradientBrush>(() =>
            {
                var linearGradientBrush = new LinearGradientBrush { ...};
                linearGradientBrush.GradientStops.Add( ... );
                linearGradientBrush.GradientStops.Add( ... );

                return linearGradientBrush;
            }
        );

    public static LinearGradientBrush MyBrush
    {
        get { return _myBrush.Value; }
    }
}

Ответ 1

Я бы использовал Lazy<T> в целом:

  • Это поточно-безопасный (может быть, не проблема в этом случае, но будет в других)
  • Это делает очевидным, что происходит только по имени
  • Он позволяет null быть допустимым значением

Обратите внимание, что вам не нужно использовать выражение лямбда для делегата. Например, здесь подход, который может быть немного чище:

public static class Brushes
{
    private static readonly Lazy<LinearGradientBrush> _myBrush =
        new Lazy<LinearGradientBrush>(CreateMyBrush);

    private static LinearGradientBrush CreateMyBrush()
    {
        var linearGradientBrush = new LinearGradientBrush { ...};
        linearGradientBrush.GradientStops.Add( ... );
        linearGradientBrush.GradientStops.Add( ... );

        return linearGradientBrush;
    }

    public static LinearGradientBrush MyBrush
    {
        get { return _myBrush.Value; }
    }
}

Это особенно удобно, когда процесс создания становится сложным с помощью циклов и т.д. Обратите внимание, что по внешнему виду вы можете использовать инициализатор коллекции для GradientStops в своем коде создания.

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

Как указано в ответе DoubleDown, нет способа сбросить это, чтобы принудительно перераспределить (если вы не сделаете поле Lazy<T> не только для чтения), но я очень редко обнаружил, что это важно.

Ответ 2

Используйте Lazy<T>, поскольку он точно выражает то, что вы делаете - ленивая загрузка.

Кроме того, он сохраняет ваше свойство очень чистым и безопасным потоком.

Ответ 3

Как правило, единственная причина, по которой не использовать ленивый, - это reset переменная, равная null, поэтому следующий доступ заставляет ее снова загружаться. Lazy не имеет reset, и вам нужно будет воссоздать ленивый с нуля.

Ответ 4

Lazy<T> будет правильно переносить параллельные сценарии (если вы передадите правильный LazyThreadSafetyMode), в то время как ваш пример не имеет проверок безопасности потоков.

Ответ 5

Lazy<T> проще, он четко выражает намерение кода.
Это также безопасно для потоков.

Обратите внимание: если вы на самом деле используете это для нескольких потоков, вам нужно сделать его [ThreadStatic]; Объекты GDI + не могут быть разделены между потоками.

Ответ 6

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

С точки зрения проверки, Lazy хорошо протестирован и проверен артефактом.

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

Ответ 7

Хорошо, если ваша производительность примерно такая же, единственная причина использовать Lazy<T> над кешированной версией будет, если вы не уверены, действительно ли пользователь загрузит свойство.

Точкой Lazy<T> является ожидание, пока пользователь не потребует ресурс, а затем создаст его в этом экземпляре во времени. Если им всегда нужен ресурс, тогда нет смысла использовать Lazy<T>, если вам не нужны некоторые другие цели, такие как потокобезопасность.