Lazy Loading in C#
Every programming language provides a primitive data type that can be used for storing data. And when we need to club data together, developers go for custom classes that use primitive data types inside it. And there will be scenarios where the custom class can make use of another custom class inside it. Let’s explain this with an example.
Consider a class ‘Student’, which has properties like first name, last name, date of birth, etc. It also includes another custom class called ‘Address.’ The address and student are linked, but they are present in different classes. Here, there will be scenarios where the Student class will be used, but the need for Address information will not be necessary. Even in such cases, the object of address gets initialized and occupies memory.
Now imagine if this address object holds a large amount of data - it will be unnecessarily consuming memory while we try to access the properties of Student such as first name, last name and etc…,
To make the application perform better, C# 4.0 version introduced the feature of lazy loading. Let's see in detail what happens when we don't opt for it and also understand how the code looks like when we embrace the Lazy loading pattern.
Table of contents
- What is lazy loading?
- Understanding lazy loading in C#
- What's Next?
What is Lazy Loading?
Lazy loading is a concept of delaying the initialization of objects, which are initialized based on demand. This feature comes in handy while the developers are working with large objects and they are unused. In other words, it is very handy in areas where the object creation is costly, but the created object is not being utilized. It improves the performance of the application and the application design. In Lazy loading, the object is not initialized until the first time it is needed.
Understanding lazy loading in C#
Before we look into the Lazy loading implementation, let’s create a normal code and understand the object instantiation using that. For easier understanding, we are going to implement the example that's discussed in the introduction section.
using System;
namespace PARTECH_Lazyloading
{
class Program
{
static void Main(string[] args)
{
var studentDetails = new Student("Max", "Verstappan");
Console.ReadLine();
}
}
public class Student
{
public Student(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
AddressDetail = new Address("Line 1");
}
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
public Address AddressDetail { get; set; }
}
public class Address
{
public Address(string addr1)
{
AddressLine1 = addr1;
}
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Country { get; set; }
}
}
In the above code, When the Student class gets initialized, the Address class also gets initialized. Lazy load is not happening here, and in case if the Address details are huge, then it consumes a lot of memory. Even when the address object is not being utilized, the memory for the object would be assigned automatically.
And if we do not instantiate the address details on the initialization of the class, then the address object would be null, but when the data gets assigned, the memory gets assigned immediately. Here is an example of that.
using System;
namespace PARTECH_Lazyloading
{
class Program
{
static void Main(string[] args)
{
var studentDetails = new Student("Max", "Verstappan");
studentDetails.AddressDetail = new Address("Line 1");
Console.ReadLine();
}
}
public class Student
{
public Student(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
public Address AddressDetail { get; set; }
}
public class Address
{
public Address(string addr1)
{
AddressLine1 = addr1;
}
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Country { get; set; }
}
}
The moment the address object gets initialized, the memory gets allocated.
Now, let us see how to implement lazy loading with the same example. We are going to use the same example and implement a lazy loading feature for the Address class. While we create the student class, the address class gets initialized, but the memory is assigned at that moment. But, whenever the data is required, the value is fetched from it, and memory is assigned.
using System;
using System.Collections.Generic;
namespace PARTECH_Lazyloading
{
class Program
{
static void Main(string[] args)
{
var studentDetails = new Student("Max", "Verstappan");
Console.WriteLine("Is Address detail memory assigned - " + studentDetails.AddressDetail.IsValueCreated);
studentDetails.GetAddress();
Console.WriteLine("Is Address detail memory assigned - " + studentDetails.AddressDetail.IsValueCreated);
Console.ReadLine();
}
}
public class Student
{
public Student(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
AddressDetail = new Lazy<List<Address>>();
}
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
public Lazy<List<Address>> AddressDetail { get; set; }
public void GetAddress()
{
var data = new Lazy<List<Address>>();
data.Value.Add(new Address("Line 1"));
AddressDetail = data;
}
}
public class Address
{
public Address(string addr1)
{
AddressLine1 = addr1;
}
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Country { get; set; }
}
}
In the above code, if we keep a breakpoint just after the student object creation and look at the memory assignment for the Address class, the Value will be present as Null only. This is in spite of the fact that we have initialized the Address class in the constructor.
Now, we log whether the object is created or not. And in the next line, we are calling the getAddress() method. If we check the same object creation after the address assignment, then it will return true now.
This indicates that the memory of the object is assigned only after the object is used. The below image indicates the memory assignment from the console window.
What’s Next?
Lazy loading is a handy feature while working with complex classes with large data, and it also provides thread safety by default. So go ahead and use it in your application to improve its performance.