The Importance of Being Lazy

Firstly, apologies are in order…I couldn’t resist the incredibly cheesy title. Secondly, this post has absolutely nothing to do with being lazy (a trait I’m sure is attached to us developers on a regular basis, ahem!), in the traditional sense at least. Nope, today we’ll be looking at the lazy keyword in C# and trying to get an understanding as to how it can be used.

A ‘Lazy’ Explanation

Right, that was the last crap joke and attempt to poke fun at the term ‘lazy’, I promise! So what does this keyword entail and how is it used? The most rudimentary packaged answer is that it allows the developer the necessary control to defer the creation of an ‘expensive’ object (or delay a resource intensive operation) until required by calling code. You may, for example, have a property within one of your classes that although a perfectly valid member (i.e. has a rightful place within the object) may only rarely be required by other members utilising this object. If you couple this with the fact that this object property, hypothetically, at least, is very expensive to create (i.e. a large list or array, a dataset requiring interaction with a database, etc.) then you may find the lazy keyword useful. Here are some further key points to give you a solid grounding in the basics:

  • When used in the appropriate context you can open up opportunities to increase application performance and reduce memory requirements (with the added bonus of easing the pressure on the garbage collector).
  • Allows you to avoid unrequired computations. It’s worth noting here that anything created using the lazy keyword operates on a single instance basis (i.e. you always get returned the same object or value used to initialise the member you are accessing). Examples of this in action are coming right up, so hold on to your hats.
  • Getting access to a lazy members value is, unsurprisingly, achieved via using the ‘.Value’ property (e.g. from the get accessor of a property). More on this later again.
  • Interactions with objects instantiated using the lazy keyword are, by default, thread-safe. There are a few extra discussion points to cover here, which will follow shortly.
  • Ability to write lambdas and use anonymous methods to instantiate a member, writing complex initialisation logic as needed.

I want to scratch the surface on a couple of these points before we proceed to the examples; mainly as I feel a bit more of a detailed discussion wouldn’t go amiss. Firstly, after a root around by myself and a friend at work, the default implementation of a lazy objects thread-safety mechanic (as hinted at in the MSDN documentation) seems to be potentially resource intensive (although hopefully wouldn’t rival the overhead of the objects you intend to work with using this method!). Also, there is the possibility of deadlocks to contend with, albeit a fairly small chance. As we stood on the edge of the diving board and took the plunge, we quickly realised (we did a bit of decompiling to discover this) that thread safety, as far the lazy class goes, involves a fair few lines of code and the utilisation of Monitor.Enter. The end result here is that, depending on usage, you actually may find that you want to alter how locks on lazy objects work (no fast and hard evidence of course, I’m just making you aware of the information should you want to delve deeper and make a more informed decision further down the line).

As an alternative, you can use a constructor consuming a LazyThreadSafetyMode enum value and passing LazyThreadSafetyMode.PublicationOnly through as the value. This provides an alternative strategy to the default locking, whereby all threads ‘race’ to initialise the value (when a member is called simultaneously). One thread initialises the value, and all other threads receive a copy of the ‘same’ object, again only if the ‘Value’ property is called at the same time by multiple threads. Instances created, that ultimately don’t get used, are discarded. As eluded to on MSDN, the end result here is that you mitigate the locking overhead but run the risk of creating and discarding extra copies of an object that is expensive to create (again, it’s stated that this eventuality is unlikely). The end result here is to take all of this onboard and choose your path wisely (a little like picking the right cup in Indiana Jones and the Last Crusade, hopefully without the same severe consequences!).

Something else that could potentially catch you out is, when you use factory methods to initialise a lazy object, that exceptions are cached. In essence, if an exception is thrown the first time a thread accesses a lazy objects Value property this same exception will continue to be thrown on every call (even if conditions change that would otherwise result in a successful call further down the line). This protects us against scenarios whereby we could get subtly different results on different threads and introduces the consistency of ‘on every call, you get the same result’, something which certainly makes sense. A poignant point to be aware of at the very least. MSDN states to add retry logic into the instantiation routine (e.g. the factory method); add this concept to the kit bag at any rate.

Hopefully, this gives you an idea that this, conceptually, can be incredibly useful; with the potential, of course, to lead you quickly up shit creek if you’re not on the ball! For you further delvers and divers, here’s the MDSN documentation to keep you busy:

MDSN Lazy Class Documentation
MSDN Lazy Initialisation

Basic Lazy Usage Examples

We’ve discussed many of the finer points above, so I’ll make sure I’m sticking to business here and will try to keep this as short, and sweet, as possible.

To start us off, we have a basic outline of an Applicant class with some properties and a constructor. Simulating our ‘expensive-to-create’ member, in this case, is the ApplicantExtraDetails object. This has a private backing field and is accessible via a public property. In this implementation, the private backing field for our ApplicantExtraDetails (we could have had a normal property, initialised in the constructor, of course, but I’ve structured it this way for an easier swap out later) is ‘always’ created, regardless of whether calling code uses it.

using System;
using System.Collections.Generic;
using System.Linq;

namespace LazyObjects
{
    /// <summary>
    /// Class that outlines a basic job applicant.
    /// </summary>
    public class Applicant
    {
        #region Public Properties

        /// <summary>
        /// Gets the applicants unique identifier (to be set
        /// via the constructor).
        /// </summary>
        public Guid UniqueApplicantIdentifier { get; }

        /// <summary>
        /// Gets or sets the applicants forename.
        /// </summary>
        public string Forename { get; private set; }

        /// <summary>
        /// Gets or sets the applicants surname.
        /// </summary>
        public string Surname { get; private set; }

        /// <summary>
        /// Gets or sets the applicants core skills.
        /// </summary>
        public List<string> CoreSkills { get; private set; }

        #endregion Public Properties

        #region Expensive Field/Property

        /// <summary>
        /// Private data field that outlines an applicants extra details (a mock object only,
        /// not best practice, just for illustration). Container for other objects.
        /// </summary>
        private ApplicantExtraDetails extraDetails = new ApplicantExtraDetails();

        /// <summary>
        /// Gets the applicants extra details (expensive object to retrieve
        /// and/or hold in memory). May not be required by the caller.
        /// </summary>
        public ApplicantExtraDetails ExtraDetails
        {
            get
            {
                return extraDetails;
            }
        }

        #endregion Expensive Field/Property

        #region Constructor

        /// <summary>
        /// Initialises a new instance of the Applicant class with standard setup data.
        /// </summary>
        /// <param name="uniqueIdent">The applicants unique identifier.</param>
        /// <param name="firstName">The applicants first name.</param>
        /// <param name="lastName">The applicants surname.</param>
        /// <param name="skills">The applicants skills (basic).</param>
        public Applicant(Guid uniqueIdent, string firstName, string lastName, params string[] skills)
        {
            UniqueApplicantIdentifier = uniqueIdent;
            Forename = firstName;
            Surname = lastName;
            CoreSkills = skills.ToList();
        }

        #endregion Constructor
    }
}

Rolling on, here we have the ApplicantExtraDetails type itself, which contains two arrays (that could be, but aren’t, massive) that can hold PersonalInterest and OtherDetail objects respectively.

using System;

namespace LazyObjects
{
    /// <summary>
    /// Class that outlines a job applicant extra details (that may not always be 
    /// required by the caller).
    /// </summary>
    public class ApplicantExtraDetails
    {
        #region Private Data Fields

        /// <summary>
        /// Private data field that outlines an applicant’s personal interests (a mock object only,
        /// not best practice, just for illustration).
        /// </summary>
        private PersonalInterest[] personalInterests = new PersonalInterest[2] { new PersonalInterest(), new PersonalInterest() };

        /// <summary>
        /// Private data field that outlines an applicant’s other details (a mock object only,
        /// not best practice, just for illustration). 
        /// </summary>
        private OtherDetail[] otherDetails = new OtherDetail[2] { new OtherDetail(), new OtherDetail() };

        #endregion Private Data Fields

        #region Public Properties

        /// <summary>
        /// Gets an applicant’s personal interests.
        /// </summary>
        public PersonalInterest[] PersonalInterests
        {
            get
            {
                return personalInterests;
            }
        }

        /// <summary>
        /// Gets an applicant’s other details.
        /// </summary>
        public OtherDetail[] OtherDetails
        {
            get
            {
                return otherDetails;
            }
        }

        #endregion Public Properties

        #region Constructor

        /// <summary>
        /// Initialises a new instance of the ApplicantExtraDetails class with standard setup data.
        /// </summary>
        public ApplicantExtraDetails()
        {
            // Log when this class gets instantiated for illustration purposes
            Console.WriteLine("Applicant Extra Details instantiated!{0}", Environment.NewLine);
        }

        #endregion Constructor
    }
}

The PersonalInterest and OtherDetail classes themselves just contain a single string property, for illustration only, during the upcoming example.

namespace LazyObjects
{
    /// <summary>
    /// Class that outlines a job applicant personal interest (that may not always be 
    /// required by the caller - Part of the ApplicantExtraDetails).
    /// </summary>
    public class PersonalInterest
    {
        #region Getter Only Auto Property

        /// <summary>
        /// Gets the value associated with this 
        /// personal interest (hard coded for illustration).
        /// </summary>
        public string Value => "Test Personal Interest.";

        #endregion Getter Only Auto Property
    }
}

...

namespace LazyObjects
{
    /// <summary>
    /// Class that outlines a job applicant 'other detail' (that may not always be 
    /// required by the caller - Part of the ApplicantExtraDetails).
    /// </summary>
    public class OtherDetail
    {
        #region Getter Only Auto Property

        /// <summary>
        /// Gets the value associated with this 
        /// other detail (hard coded for illustration).
        /// </summary>
        public string Value => "Test Other Detail.";

        #endregion Getter Only Auto Property
    }
}

So, on to the Console Application program class that brings some of this code into scope. For starters, we generate a couple of applicants using fixed values and then add these applicants to a list for iterative processing. The crux during this iterative processing is that only the second applicants ‘Extra Details’ ultimately get interrogated and extracted in this sample…output coming up next.

using System;
using System.Linq;
using System.Collections.Generic;

namespace LazyObjects
{
    /// <summary>
    /// Console program class (for a Lazy Objects test).
    /// </summary>
    class Program
    {
        /// <summary>
        /// Main entry point method.
        /// </summary>
        /// <param name="args">Arguments passed to this application.</param>
        static void Main(string[] args)
        {
            // Generate two unique identifiers for our applicants
            Guid applicantOneUniqueReference = Guid.NewGuid(), applicantTwoUniqueReference = Guid.NewGuid();

            // Generate two test 'applicants' - This data, along with the identifiers, would have been sourced from a database in this example (just hard coded for reference)
            Applicant applicantOne = new Applicant(applicantOneUniqueReference, "James", "Jones", new string[] { "C#", "F#", "C++" }), 
                applicantTwo = new Applicant(applicantTwoUniqueReference, "Brenda", "Reed", new string[] { "HTML", "CSS", "jQuery" });

            // Start processing the applicants...
            Console.WriteLine("Processing applicants...{0}", Environment.NewLine);

            // Doing a foreach to rip out the firstname/surname of each applicant
            List<Applicant> applicants = new List<Applicant>();
            applicants.AddRange(new Applicant[] { applicantOne, applicantTwo });
            applicants.ForEach(app => 
            {
                Console.WriteLine("Firstname: {0}", app.Forename);
                Console.WriteLine("Surname: {0}{1}", app.Surname, Environment.NewLine);

                // Only get and process the 'expensive details' for applicant two
                if (app.UniqueApplicantIdentifier == applicantTwoUniqueReference)
                {
                    app.ExtraDetails.PersonalInterests.ToList().ForEach(item => Console.WriteLine(item.Value));
                    Console.WriteLine();

                    app.ExtraDetails.OtherDetails.ToList().ForEach(item => Console.WriteLine(item.Value));
                    Console.WriteLine();
                }
            });

            // Finished this processing run
            Console.WriteLine("Finished processing...");
            Console.ReadLine();
        }
    }
}

Due to a little logging placed in the constructor of the ApplicantExtraDetails, you can clearly see here that (as we fully expect) both applicants have initialised an object that is not always required.

Console output from a test using no Lazy objects.
Test Using No Lazy Objects.

Ok, a no brainer I hear you say; bread and butter examples always provide a good ‘base camp’ to start from, though. So the problem here is that the object representing the first applicant has been backed into a corner whereby it has been forced to create an ApplicantExtraDetails object on type initialisation, even though only the second applicants ApplicantExtraDetails object property is being used. Let’s change it up to be a little lazier.

Using Lazy Object Instantiation

A minor change has been made to the implementation of the Applicant class to use the generic lazy type. The key thing to note here is that, with the ExtraDetails property, you need to call the ‘.Value’ property to actually obtain the relevant details to return to the caller.

#region Expensive Field/Property

/// <summary>
/// Private data field that outlines an applicant’s extra details (a mock object only,
/// not best practice, just for illustration). Container for other objects. Now using
/// full, Lazy instantiation.
/// </summary>
private Lazy<ApplicantExtraDetails> extraDetails = new Lazy<ApplicantExtraDetails>();

/// <summary>
/// Gets the applicants extra details (expensive object to retrieve
/// and/or hold in memory). May not be required by the caller. Note the 
/// use of .Value now (since the backing field utilised Lazy<T>)./>
/// </summary>
public ApplicantExtraDetails ExtraDetails
{
	get
	{
		return extraDetails.Value;
	}
}

#endregion Expensive Field/Property

The end result is illustrated here, whereby we used exactly the same program class listing above. Two things to note…firstly, notice that the ‘Applicant Extra Details instantiated’ only occurs once, for our second applicant, which is bonza. However, you should also pick up on the fact that the logging of this message now occurs further ‘down the road’ in the application’s lifetime, a nod to the fact that the creation of the object was deferred until it was required.

Console output from a test using Lazy objects.
Test Using Lazy Objects.

For our last wheel spin and handbrake turn, take a look at the last example showing initialisation of a lazy field, using an anonymous method (i.e. factory method) to create the goods.

/// <summary>
/// Example of a static lazy data field.
/// </summary>
private static Lazy<List<string>> lazyStringList = new Lazy<List<string>>(() =>
{
	Console.WriteLine("Getting lazyStringList Value!{0}", Environment.NewLine);

	List<string> newLazyList = new List<string>();
	newLazyList.AddRange(new string[] { "Test one", "Test two", "Test three" });

	return newLazyList;
});

/// <summary>
/// Main entry point method.
/// </summary>
/// <param name="args">Arguments passed to this application.</param>
static void Main(string[] args)
{
	LazyListTest();
}

/// <summary>
/// Lazy static data field test.
/// </summary>
private static void LazyListTest()
{
	// Initialise a couple of lists (initialisation, as the lazy field is static, will occur only once (returns a reference to the same object))
	List<string> testOne = lazyStringList.Value, testTwo = lazyStringList.Value;

	// Iterate over both lists, using an extension
	Console.WriteLine("Iterating over testOne values:");
	testOne.ListItemsToConsole();

	Console.WriteLine("{0}Iterating over testTwo values:", Environment.NewLine);
	testTwo.ListItemsToConsole();

	Console.ReadLine();
}

...

using System;
using System.Collections.Generic;

namespace LazyObjects
{
    /// <summary>
    /// Static extensions class, for illustration only.
    /// </summary>
    public static class Extensions
    {
        /// <summary>
        /// Pushes list items to the console.
        /// </summary>
        /// <param name="targetList">The List to operate on (of the type specified, would not to restrict by type a little more in the real world).</param>
        public static void ListItemsToConsole<T>(this List<T> targetList)
        {
            // We could throw an exception here alternatively, if the list was not correctly instantiated - Preference here just to skip over however
            if (targetList != null && targetList.Count > 0)
            {
                targetList.ForEach(item => Console.WriteLine(item));
            }
        }
    }
}

Here’s the output. The thing to run with here is that the testOne and testTwo lists have both been set using lazyStringList.Value but the factory method initialising lazyStringList was only called once. Remember lazy objects are accessed via the ‘.Value’ property as read-only essentially and the same object is returned on subsequent calls.

Console output from a test using Lazy objects statically.
Test Using Lazy Objects Statically.

I hope this has been a useful tour de force on the subject of lazy objects and, as always, happy coding until the next time!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.