0

I recently started trying to use the Lazy class to implement lazy loading. However, I'm finding that doing so does not seem to have any advantages over simply implementing it myself via a null-coalescing operator.

For example, if I want to have Task lazy-load a Contact, I can do it like so:

    public class Task
    {
        [NotNull] private readonly IContactRepository _contactRepo;
        [NotNull] private readonly Lazy<Contact> _lazyContact;
        public readonly int? ContactFk;

        public Task([NotNull] IContactRepository contactRepo, int? contactFk)

        {
            ContactFk = contactFk;
            _contactRepo = contactRepo;

            _lazyContact = new Lazy<Contact>(LoadContact);
        }

        [CanBeNull]
        public Contact Contact => _lazyContact.Value;

        [CanBeNull]
        private Contact LoadContact() => !ContactFk.HasValue
            ? null
            : _contactRepo.GetContact(ContactFk.Value) ??
              throw new DataException($"No Contact found for Id {ContactFk}");
    }

However, previously, I was accomplishing it like so:

    public class Task
    {
        [NotNull] private readonly IContactRepository _contactRepo;
        [CanBeNull] private Contact _lazyContact;
        public readonly int? ContactFk;

        public Task([NotNull] IContactRepository contactRepo, int? contactFk)

        {
            ContactFk = contactFk;
            _contactRepo = contactRepo;
        }

        [CanBeNull]
        public Contact Contact => !ContactFk.HasValue
            ? null
            : _lazyContact ?? (_lazyContact =
                  _contactRepo.GetContact(ContactFk.Value) ??
                  throw new DataException($"No Contact found for Id {ContactFk}"));
    }

Which to me seems to be more straight-forward. Instead of three fields, a property, a method, and an instantiation, it's just three fields and a property.

Is there something I'm missing to make the use of Lazy smoother? Some sort of extension method I can add or something?

Or should I just ignore Lazy and keep doing things as I have been?

Or does it depend on the circumstance?

1
  • Your two methods are not equivalent: Lazy<T> also guarantees that the element will not be initialised more than once, and is thread-safe. Sep 25, 2019 at 16:33

2 Answers 2

2

Those two versions do different things.

Consider what happens if the DataException is thrown and caught. The first will search _contactRepo at most once. The second will search _contactRepo each time the property is accessed.

0

Thanks to Caleth and VisualMelon for pointing out how Lazy has certain advantages (thread safety, not repeatedly querying if the result is null or exception) over a naive null-coalescing approach.

For convenience, I've also created a slightly improved version that uses a null-coalesced Lazy:

public class Task
{
    [NotNull] private readonly IContactRepository _contactRepo;
    [CanBeNull] private readonly Lazy<Contact> _lazyContact;
    public readonly int? ContactFk;

    public Task([NotNull] IContactRepository contactRepo, int? contactFk)
    {
        ContactFk = contactFk;
        _contactRepo = contactRepo;
    }

    [CanBeNull]
    public Contact Contact =>
    (
        _lazyContact ?? (_lazyContact = new Lazy<Contact>(() =>
            !ContactFk.HasValue
                ? null
                : _contactRepo.GetContact(ContactFk.Value) ??
                  throw new DataException($"No Contact found for Id {ContactFk}")))
    ).Value;
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.