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

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube