Cached property vs Lazy<T>
Asked Answered
T

7

56

In .NET 4 the following snippet with a cached property can also be written using the System.Lazy<T> class. I measured the performance of both approaches and it's pretty much the same. Is there any real benefit or magic for why I should use one over the other?

Cached Property

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;
        }
    }
}

Lazy<T>

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; }
    }
}
Teasley answered 27/2, 2011 at 17:47 Comment(3)
By using Lazy<T> you're being Lazy to write your own implementation. (In a good way, of course.)Chokecherry
Interesting, I was inclined to say it's less code and more readable, but your example demonstrates this isn't quite the case. But then again, I already have a Property<T> class for backing fields that supports this and more common backing field behavior.Logography
Lazy<T> allows thread safetyBrythonic
P
77

I would use Lazy<T> in general:

  • It's thread-safe (may not be an issue in this case, but would be in others)
  • It makes it obvious what's going on just by the name
  • It allows null to be a valid value

Note that you don't have to use a lambda expression for the delegate. For example, here's an approach which may be slightly cleaner:

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; }
    }
}

This is particularly handy when the creation process gets complicated with loops etc. Note that by the looks of it, you could use a collection initializer for GradientStops in your creation code.

Another option is not to do this lazily, of course... unless you have several such properties in your class and you only want to create the relevant objects on a one-by-one basis, you could rely on lazy class initialization for many situations.

As noted in DoubleDown's answer, there's no way of resetting this to force recomputation (unless you make the Lazy<T> field not readonly) - but I've very rarely found that to be important.

Penuche answered 27/2, 2011 at 17:49 Comment(6)
In what case would be using Lazy an issue ?Trisyllable
@user256034: When you're not using .NET 4 :)Penuche
Thanks! The 3 points in your list are exactly what I was searching for ;)Teasley
Good list of points, if you agree with the answer of @DoubleDown and would consider adding their point to your answer, I will upvote :)Discounter
@tsemer: It's not something I've personally found useful, but I've added a small note.Penuche
Fair enough, but as it is a difference I wanted it to appear in the answer I'm upvoting. Upvoted :)Discounter
H
7

Use Lazy<T>, as it expresses exactly what you are doing - lazy loading.

In addition, it keeps your property very clean and is thread safe.

Harwell answered 27/2, 2011 at 17:49 Comment(0)
M
4

Typically the only reason to not use lazy is to reset the variable to null so the next access causes it to load again. Lazy has no reset and you'd need to recreate the lazy from scratch.

Mertens answered 27/2, 2011 at 18:59 Comment(2)
See this answer https://mcmap.net/q/365778/-reset-system-lazy/…Stagg
Good point! IMHO, this in combination with @JonSkeet's answer is the full answer.Discounter
S
2

Lazy<T> will correctly handel concurrent scenarios (if you pass in the correct LazyThreadSafetyMode ) while your example does not have any thread-safety checks.

Sofia answered 27/2, 2011 at 17:49 Comment(0)
A
1

The Lazy<T> is simpler—it clearly expresses the intent of the code.
It's also thread safe.

Note that if you're actually using this on multiple threads, you need to make it [ThreadStatic]; GDI+ objects cannot be shared across threads.

Antipyrine answered 27/2, 2011 at 17:49 Comment(0)
B
0

Lazy has some syncronization overhead to provide thread-safety whereas cached property is initiliazed by CLR way before any other code and you do not need to pay synronization cost

From a testability point of view, Lazy is well tested and proven artifact.

However, it has a very slight overhead, in my opinion, over other option

Barbados answered 7/10, 2011 at 9:53 Comment(1)
Lazy supports a 'None' thread-safety mode.Behold
T
-1

Well if your performance is about the same then the only reason to use Lazy<T> over the cached version would be if you aren't sure if the user is actually going to load the property.

The point of Lazy<T> is to wait until the user needs the resource and then create it at that instance in time. If they are always going to need to resource then there is no point in using Lazy<T>, unless you need some of it's other purposes such as it being thread safe.

Thaumaturgy answered 27/2, 2011 at 17:51 Comment(1)
-1, OP's alternative is identical in the sense that if the property is never called then the list is never instantiated. In that sense they are both "lazy".Discounter

© 2022 - 2024 — McMap. All rights reserved.