C# Corner

Leverage Lazy Loading in .NET 4.0

Lazy Loading is a programming pattern useful for resource-intensive objects.

Lazy loading is a common design pattern often used for constructing resource-intensive objects. It's  also frequently used in conjunction with the singleton, and/or factory patterns. Lazy loading entails constructing an object at the point it's needed, rather than when it's declared. If the object never ends up being used, it will never be fully constructed.

In versions of Microsoft's .NET Framework prior to 4.0, one would have to create their own lazy loading logic to take advantage of this pattern. Fortunately, .NET 4.0 includes Lazy<T>, which is a generic implementation of the lazy loading pattern. Let's first look at how to use Lazy<T> to implement the singleton pattern. The singleton pattern creates just one instance ever for an object; and it's often used for constructing objects that utilize scare resources.

Creating the Lazy Singleton

To create our lazy loaded singleton class, we create a static read-only Lazy<T> instance of our class. We also make the constructor private to prevent it from being called in a derived class. An instance of the class will only ever be created if the Instance property of the class is accessed. We leverage Lazy<T> to ensure this behavior. The Lazy<T> class defers initialization of its class until the .Value property is accessed. This particular implementation of the lazy initialization is known more commonly as the value holder pattern. The Lazy<T> Value property is a place holder that is only loaded at the point it is invoked.

namespace VSMLazyLoading.Common
{
    public class Singleton
    {
        private static readonly Lazy m_instance = new Lazy(() => new Singleton());

        public int MyProperty { get; set; }

        private Singleton()
        {
        }

        public static Singleton Instance
        {
            get
            {
                return m_instance.Value;
            }
        }
    }

Alternate Lazy Singleton

The Lazy<T> class also allows for similar behavior out-of-the-box  through use of an overloaded constructor. For example, say we have a ServiceProxy class used to consume a service endpoint that we only ever want to initialize once in a thread-safe manner.

First, we create our fake service proxy class.

namespace VSMLazyLoading.Common
{
public class ServiceProxy
{
public ServiceProxy()
{
Console.WriteLine("Service proxy created on {0}", DateTime.Now);
}

        public string SayHello(string name)
{
return String.Format("Hello, {0}", name);
}
}
}

Next we construct our lazy loaded service proxy class by utilizing the Lazy<T> class's ability to specify a value holder function and execution method. The LazyThreadSafetyMode.ExecutionAndPublication flag will ensure that only one thread may initialize the object and that only one instance is ever created.

Lazy<ServiceProxy>  proxy = new Lazy<ServiceProxy>(() => new  ServiceProxy(), System.Threading.LazyThreadSafetyMode.ExecutionAndPublication);
Console.WriteLine(String.Format("Lazily loaded ServiceProxy on {0}", DateTime.Now));
System.Threading.Thread.Sleep(1000);
string message =  proxy.Value.SayHello("Eric");
Console.WriteLine(String.Format("Message received: {0}", message));
message = proxy.Value.SayHello("John");
Console.WriteLine(String.Format("Message received: {0}", message));
Console.ReadLine();

[Click on image for larger view.]
Figure 1. Creation of the ServiceProxy class.

As you can see in Figure 1, the ServiceProxy class was only constructed at the point at which the SayHello method was first invoked.

Simple Object Lazy Loading

Often, you may not need a singleton of class but would like to lazily load an object or child object instance. First, let's look at how to lazy load an entire simple class and then look at how to lazy load a more complex object that contains a child object. First let's create a test class that will contain a person's information that we would like to lazily load.

namespace VSMLazyLoading.Common
{
public class Person
{
public int PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }

        public Person()
{
Console.WriteLine(String.Format("Person created on {0}", DateTime.Now));
}

        public override string ToString()
{
return String.Format("{0} {1}", FirstName, LastName);
}
}
}

Next we implement a fake repository to load our Person object.

namespace VSMLazyLoading.Common
{
public class PersonRepository
{
public Person Load(int personId)
{
Lazy<Person> lazyPerson = new Lazy<Person>();
System.Threading.Thread.Sleep(1000);
lazyPerson.Value.FirstName = "Homer";
lazyPerson.Value.LastName = "Simpson";
lazyPerson.Value.PersonId = 1;
return lazyPerson.Value;
}
}
}

We utilize the default behavior of Lazy<T> to lazily load an instance of the Person class.

Console.WriteLine(String.Format("Created lazy initialized Person instance at  {0}.", DateTime.Now));
PersonRepository personRep = new PersonRepository();
Person person = personRep.Load(1);
Console.WriteLine(String.Format("Person: {0}", person));
Console.ReadLine();

[Click on image for larger view.]
Figure 2. The Person class.

As you can see in Figure 2, an instance of the Person class is only created at the point at which the Value property of the  Lazy<Person> instance is accessed. The Thread.Sleep call may be removed, and was only added to emphasize the point that the person object instantiation is being deferred until the Value property is set.

Complex Object Lazy Loading

Now let's look at how to defer only loading of an entire complex object. Our complex object is an Employee record that contains an associated address record. We want to lazily load both the person as well its child address record.

First let's create our Address class.

  namespace VSMLazyLoading
{
public class Address
{
public int AddressId { get; set; }
public int PersonId { get; set; }
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
public string Country { get; set; }

        public Address()
{
Console.WriteLine(String.Format("Address created on {0}.", DateTime.Now));
}

        public override string ToString()
{
return AddressLine1 + Environment.NewLine + AddressLine2 + Environment.NewLine
+ City + " " + State + ", "
+ ZipCode + Environment.NewLine + Country;
}
}

Next we'll create our Employee class, which derives from our already created Person class.
namespace VSMLazyLoading.Common
{
public class Employee : Person
{
private Lazy<Address> m_address;

        public Lazy<Address> LazyAddress
{
set
{
m_address = value;
}
}

        public Address MyAddress
{
get
{
return m_address.Value;
}
}
}

Next we create our fake AddressRepository class that will load an Address record for a given personId.

 namespace VSMLazyLoading.Common
{
public class AddressRepository
{
public Address LoadForPerson(int personId)
{
Address address = new Address()
{
AddressId = 1,
PersonId = personId,
AddressLine1 = "1234 Fake Street",
AddressLine2 = "Sector 7G",
State = "IL",
ZipCode = "60652",
Country = "USA"
};

            return address;
}
}

Now we create our fake EmployeeRepository class, which will lazily load an employee by their personId. We also lazily load the employee's address record.

namespace VSMLazyLoading.Common
{
public class EmployeeRepository
{
public Lazy<Employee> Load(int personId)
{
AddressRepository addressRep = new AddressRepository();
Lazy<Employee> lazyEmployee =
new Lazy<Employee>(() => new Employee()
{
PersonId = personId,
FirstName = "Eric",
LastName = "Vogel",
LazyAddress =
new Lazy<Address>(() => addressRep.LoadForPerson(personId))
});

            return lazyEmployee;
}
}

Finally we utilize our EmployeeRepository to lazily load our fake Employee record.

EmployeeRepository employeeRep = new EmployeeRepository();
Lazy<Employee> lazyEmployee = employeeRep.Load(1);
System.Threading.Thread.Sleep(1000);
Employee employee = lazyEmployee.Value;
Console.WriteLine(String.Format("Employee info{0} {1} {2}", Environment.NewLine, employee.FirstName, employee.LastName));
System.Threading.Thread.Sleep(1000);
Address employeeAddress = employee.MyAddress;
Console.WriteLine(String.Format("Address info:{0}{1}",Environment.NewLine, employeeAddress));
Console.ReadLine();

[Click on image for larger view.]
Figure 3. Lazily loading the Employee record.

As you can see in Figure 3, our Employee record has successfully been lazily loaded as well as its associated Address record.

Get Lazy
As you can see lazy initialization and lazy loading are two very useful programming patterns. With the introduction of the Lazy<T> implementation in the fourth iteration of the .NET Framework, utilization of these programming patterns has become much more widely accessible. As with all programming patterns, lazy initialization and loading should be used judiciously.

About the Author

Eric Vogel is a Senior Software Developer for Red Cedar Solutions Group in Okemos, Michigan. He is the president of the Greater Lansing User Group for .NET. Eric enjoys learning about software architecture and craftsmanship, and is always looking for ways to create more robust and testable applications. Contact him at [email protected].

comments powered by Disqus

Featured

  • AI for GitHub Collaboration? Maybe Not So Much

    No doubt GitHub Copilot has been a boon for developers, but AI might not be the best tool for collaboration, according to developers weighing in on a recent social media post from the GitHub team.

  • Visual Studio 2022 Getting VS Code 'Command Palette' Equivalent

    As any Visual Studio Code user knows, the editor's command palette is a powerful tool for getting things done quickly, without having to navigate through menus and dialogs. Now, we learn how an equivalent is coming for Microsoft's flagship Visual Studio IDE, invoked by the same familiar Ctrl+Shift+P keyboard shortcut.

  • .NET 9 Preview 3: 'I've Been Waiting 9 Years for This API!'

    Microsoft's third preview of .NET 9 sees a lot of minor tweaks and fixes with no earth-shaking new functionality, but little things can be important to individual developers.

  • Data Anomaly Detection Using a Neural Autoencoder with C#

    Dr. James McCaffrey of Microsoft Research tackles the process of examining a set of source data to find data items that are different in some way from the majority of the source items.

  • What's New for Python, Java in Visual Studio Code

    Microsoft announced March 2024 updates to its Python and Java extensions for Visual Studio Code, the open source-based, cross-platform code editor that has repeatedly been named the No. 1 tool in major development surveys.

Subscribe on YouTube