Any good reasons to not use null-coalescing operator for lazy initialization?
Asked Answered
V

4

15

Greetings I was doing some lazy initialization code today, and thought why not use the null-coalescing operator to do this, it is shorter, but then I thought is there any overhead or additional cost to doing it this way.

Below is simplified sample code showing a more common form used for lazy initialization, and then one using null-coalescing operator. They have the exact same results, and appear equivalent. My first thoughts are that after the object has been created there is now an additional assignment of it to itself using ??. Is this a non-issue and the compiler/JIT optimizes this some how, is there something more nefarious going on and you should never do lazy initialization with ??, or it is perfectly safe and no bad mojo can come from it.

private MyLazyObject _lazyObject;

public MyLazyObject GetMyLazyObjectUsingMoreCommonMethod()
{
    if (_lazyObject != null)
        return _lazyObject;

    _lazyObject = new MyLazyObject();

    return _lazyObject;
}

public MyLazyObject GetMyLazyObjectUsingNullCoalescingOpMethod()
{
    _lazyObject = _lazyObject ?? new MyLazyObject();
    return _lazyObject;
}
Valtin answered 13/9, 2011 at 21:29 Comment(8)
You could even shorten it further: return _lazyObject ?? (_lazyObject = new MyLazyObject()); Is that still readable? YMMV.Saddleback
If you reverse the check in your first method and do if(_lazyObj == null) _lazyObj = new MyLazyObj(); return _lazyObj;, it will be both more readable and shorter.Feoffee
Wouldn't that just be: return _lazyObject ?? new MyLazyObject());Wampler
@Foo He needs the variable assignment in there to store the new instance.Feoffee
@marc yep, that's how I do it :pWellwisher
Right, I missed that part. THanksWampler
@Ann yeah that is a cleaner way to do the first method I had in my example thanks.Valtin
C#8.0 let's you reduce even further: return _lazyObject ??= new MyLazyObject();Gallman
K
16

Yes, a little thing called thread safety. The two methods you give are functionally equivalent, so the null coalescing operator is not bad in and of itself, but neither of the approaches you've listed is thread-safe, so if two threads try to call your Get method at the same time, you could end up producing two MyLazyObjects. That may not be a big deal, but it's probably not what you're hoping for.

If you're using .NET 4, just use a Lazy.

private Lazy<MyLazyObject> _lazyObject = 
    new Lazy<MyLazyObject>(() => new MyLazyObject());

public MyLazyObject MyLazyObject {get {return _lazyObject.Value;}}

The code is concise, easy to understand, and thread safe.

Kingwood answered 13/9, 2011 at 21:34 Comment(12)
@Foovanadil: That's the problem: neither one is thread safe. Thread safety is a good reason not to use either approach.Kingwood
@Foovanadil: To clarify, Lazy<T> is thread safe. See msdn.microsoft.com/en-us/library/dd642259.aspxMistrot
Side note: if your class constructor might throw an exception, you probably don't want to use the no-arg Lazy<T> constructor.​ That will throw a TargetInvocationException. Instead use the Lazy<T> constructor that takes a Func<T> delegate (lambda), as in @StriplingWarrior's example. That will cache and rethrow your exception.Mistrot
Thanks @Kingwood learn something new about .Net 4 everyday, I love the Lazy<T> class.Valtin
I kinda disagree here; thread safety is a very specific topic, and the vast majority of classes do not need to be thread-safe (and indeed: if they were, it is a little more subtle than per-member). Unless thread-safety is explicitly stated as a requirement, it is artificial to introduce it (and the class is unlikely to work cohesively as a thread-safe unit unless specifically designed to do so). Lazy<T> adds a little overhead that is unnecessary most of the time.Wellwisher
@MarcGravell good point actually and I agree. In my case I am planning to have multiple threads running through this project and would impact the area I am currently working on. That is why I loved StriplingWarrior's answer as it was very applicable to my situation. However I didn't specifically mention Thread Safety in my question, and I have a few "correct" answers and I wish I could check more than one answer as correct. Since this answer gave me a solution I ended up basing my implementation on I choose it. Future readers should hopefully pick the answer that best fits there situation.Valtin
@Rodney if threading is a concern, then this is indeed a very apt answerWellwisher
@Saddleback Gravell: +1 Well argued. Depending on how the class is intended to be used, Lazy might not be the best solution. In my personal experience, lazily-instantiated objects are often intended to be used by multiple threads, but that's not the case for everyone. It just that when somebody asks "Is this perfectly safe?" I think it's worth introducing the possibility even if it wasn't an explicit requirement.Kingwood
Also you can use LazyThreadSafetyMode.None to disable thread safety overhead in Lazy<T> and still have code that is easy to understand the intentions of and could enable thread safety if need later.Valtin
I understand that Lazy<T> is thread safe. My point was that his question made no indication that threading was a concern or that threading was a problem he was trying to solve. Responding with "thread safety" just seems like the wrong thing to me. Neither of his approaches or solutions change the threading behavior at all. I agree with Marc here. Threading is a different topic than what the user is askingWampler
So, if my class isn't concerned with thread-safety, am I better off avoiding Lazy<> and the overhead associated with it by doing the null-check technique?Ivonneivor
Yeah, probably.Kingwood
W
5

It is perfectly safe and well defined - and indeed, it means the compiler can just copy the head of the stack (dup) and store once, rather than store-field, load-field.

The only time it is a problem is c# 1.2 (.NET 1.1) where it doesn't exist.

Wellwisher answered 13/9, 2011 at 21:37 Comment(1)
What's your take on the thread safety issues StriplingWarrior, CodeCaster Matthew Abbot brought up?Valtin
F
2

The null coalescing operator in syntactic sugar. Essentially it is the same as your first example, and I don't believe the JIT compiler makes an special optimisations for it. What you should be more concerned with, is the thread-safety of your methods. The null coalescing operator, is not atomic, which means you should ensure that your MyLazyObject is instantiated in a thread-safe manner before returning.

Faucher answered 13/9, 2011 at 21:36 Comment(0)
S
2

You could even alther the code of the first method:

public MyLazyObject GetMyLazyObjectUsingMoreCommonMethod()
{
    if (_lazyObject == null)
        _lazyObject = new MyLazyObject();

    return _lazyObject;
}

This will deliver the same IL as

public MyLazyObject GetMyLazyObjectUsingNullCoalescingOpMethod()
{
    _lazyObject = _lazyObject ?? new MyLazyObject();

    return _lazyObject;
}

As said, it's just syntactic sugar.

Sideboard answered 13/9, 2011 at 21:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.