Lazy loading objects with Lazy<>

Defer the loading of large objects until require with Lazy<>

Home DailyDrop

Daily Knowledge Drop

The loading of (large) objects can be deferred until they are actually used and required using the Lazy<> class


Sample

Use case

In our use case, we have a FileEntity which contains details about a file in a specific location. There are two child entities which are properties to FileEntity:

  • FileSize: stores the file size in bytes (and just stores a double value, so is small)
  • FileContents: stores the contents of the file as a string (depending on the size of the file, this can obviously be very large)

The contents of the file will not always be used and are potentially very large - so let's look at how we can defer loading the data until it is actually used and required.


The setup

// the main file entity class
public class FileEntity
{
    // a normal FileContentsEntity private variable,
    // just wrapped in Lazy<> 
    private readonly Lazy<FileContentsEntity> _fileContents;

    public Guid Id { get; }

    public string FileLocation { get; }

    public FileSizeEntity FileSize { get; }

    // when accessed, return Lazy<FileContentsEntity>.Value
    public FileContentsEntity FileContents => _fileContents.Value;

    public FileEntity(Guid id, string fileLocation, double fileSizeInBytes)
    {
        Console.WriteLine("FileEntity constructed");

        Id = id;
        FileLocation = fileLocation;
        FileSize = new FileSizeEntity(fileSizeInBytes);

        // instead of instantiating the fileContents,
        // instantiate Lazy<> with an startup method
        _fileContents = new Lazy<FileContentsEntity>(LoadFileContents);
    }

    // method to load the large data volume
    private FileContentsEntity LoadFileContents()
    {
        return new FileContentsEntity(FileLocation);
    }
}

// simple entity to store the file size
public class FileSizeEntity
{
    public double FileSizeInBytes { get; set; }

    public FileSizeEntity(double fileSizeInBytes)
    {
        Console.WriteLine("FileSizeEntity constructed");

        FileSizeInBytes = fileSizeInBytes;
    }
}

// simple entity to store the file contents
public class FileContentsEntity
{
    public string LargeStringValue { get; set; }

    public FileContentsEntity(string fileLocation)
    {
        Console.WriteLine($"FileContentsEntity loaded from '{fileLocation}'");

        LargeStringValue = "LargeStringValue";
    }
}

To use Lazy<>, the setup is almost exactly the same as without using Lazy<>.

Output

So what does the Lazy<> actually enable, and how does it effect execution?

Console.WriteLine("== pre initialization ==");

// Two FileEntity instances are created, 
// one with a small file and one with a large file
var file1 = new FileEntity(Guid.NewGuid(), @"C:\small-file.txt", 100);
var file2 = new FileEntity(Guid.NewGuid(), @"C:\large-file.txt", 1073741824);

Console.WriteLine("== post initialization ==");
Console.WriteLine("");

// The large FileContents is accessed
Console.WriteLine("Accessing file1 contents");
var file1Location = file1.FileContents;

// only the FileLocation is accessed
Console.WriteLine("Accessing file2 location");
var fileContents = file2.FileLocation;

The output looks as follows:

== pre initialization ==
FileEntity constructed
FileSizeEntity constructed
FileEntity constructed
FileSizeEntity constructed
== post initialization ==

Accessing file1 contents
FileContentsEntity loaded from 'C:\small-file.txt'
Accessing file2 location

The key take-away from the output, is that the large contents of file2 are never loaded, as they are never used.


Thread safety

There are additional considerations regarding thread safety and Lazy<>. These are not addressed in this post, but can be read in details in the reference below.


Notes

Lazy<> provides a simple and easy to use way to load objects only when they are uses - this is especially useful when the large object is not loaded on every code path. This can lead to better performance and memory usage.


References

Lazy Class

Daily Drop 27: 09-03-2022

At the start of 2022 I set myself the goal of learning one new coding related piece of knowledge a day.
It could be anything - some.NET / C# functionality I wasn't aware of, a design practice, a cool new coding technique, or just something I find interesting. It could be something I knew at one point but had forgotten, or something completely new, which I may or may never actually use.

The Daily Drop is a record of these pieces of knowledge - writing about and summarizing them helps re-enforce the information for myself, as well as potentially helps others learn something new as well.
c# .net lazy loading