PostSharp Delayed dependency loading

Original author: Matthew D. Groves
  • Transfer
The piece of code presented below, you probably wrote more than once. And what is more likely - dozens of times. This is usually written when you need to use a repository that will provide data for your application. If you have little time and you are in a hurry, this is a great way to get something in such a way that it will be loaded into memory only when you need it, but not earlier (for example, the save operation entailed access to the serialization subsystem, however, before that it was not needed). And in fact, it turns out that this piece of code is the same on the one hand, and on the other, it has to be written more than once.
As a rule, I and many programmers prefer to use an IoC container in this place to solve such problems. However, this is not always so easy to do, especially when I program in the absence of Dependency Injection in the library that I use (WinForms, WebForms, ...). Let's see why solving this problem without using PostSharp , you will spend much more time and do more work.


So, how often did you come across this code:

private IProductRepository _productRepository;
private IProductRepository ProductRepository
{
  get
  {
    if(_productRepository == null)
    {
      _productRepository = new ProductRepository();
    }
    return _productRepository;
  }
}

* This source code was highlighted with Source Code Highlighter.


While the property is of type IPropertyRepository, the getter of this property is still rigidly tied to the ProductRepository type. If you need to change “new ProductRepository ()” to “new RevisedProductRepository ()” or “new ProductRepository (2011)”, you will need to go through all the places in the boilerplate code and update it. Of course, hands will immediately reach out to do Find-> Replace, Copy-> Paste ... However, you are all familiar with this situation, right? Firstly, you will spend a lot of time on replacements, and secondly, almost every member of your team will have to deal with this (when you have to make regular replacements). And, since everyone is already actively using IoC containers, you might think that this example is exaggerated. However, believe me, such examples are very common! Anyway, using an IoC container,

private IProductRepository _productRepository;
private IProductRepository ProductRepository
{
  get
  {
    if(_productRepository == null)
    {
      _productRepository = ObjectFactory.GetInstance();
    }
    return _productRepository;
  }
}

* This source code was highlighted with Source Code Highlighter.


Now, as you can see, all ObjectFactory takes care of creating new instances. If you needed to create new instances of the class, add parameters to the constructor and perform other operations while creating your object, this can be done in one place without affecting other parts of the program (I’ll just note “ObjectFactory” - a static class in StructureMap . However, you can use absolutely any IoC container that you like best). Now our code looks much better, and the development team should not worry much about implementation: only about the interface. This is the real inversion of dependency, because Now the class depends only on the interface, not on the implementation.
The code has become much cleaner, however it still contains checks for “null” and lazy initialization, which will be found throughout our application (if we use lazy loading quite often, of course). Also, it is not so easy for us to change the same IoC container if it has ceased to suit us. And if at some point you decided that checking for null is not enough (for example, your program has become multi-tasking), you will again have to go through the whole code and change, change, change ... Let's see how you can finally solve this problem using PostSharp :

[LoadDependency] private IProductRepository _productRepository;

[Serializable]
public sealed class LoadDependencyAttribute : LocationInterceptionAspect
{
  public override void OnGetValue(LocationInterceptionArgs args)
  {
    args.ProceedGetValue(); // fetches the field and populates the args.Value
    if (args.Value == null)
    {
      var locationType = args.Location.LocationType;
      var instantiation = ObjectFactory.GetInstance(locationType);

      if (instantiation != null)
      {
        args.SetNewValue(instantiation);
      }
      args.ProceedGetValue();
    }
  }
}

* This source code was highlighted with Source Code Highlighter.


Now we have a declarative approach to using dependencies. You just mark any field with the “LoadDependency” attribute, and it will be initialized by the IoC container during the first call (see the first line). Thus, you protected yourself from constantly writing boilerplate code for each property that would use lazy-loading, null checks, thread-safety, and other other changes that you would have to make, you write each property separately. Now all your actions will be in one class.
Using a very simple and small (only 19 lines) aspect, we compressed the resulting code from 12 lines to one. We removed a lot of pointlessly repeating code ( DRY ) and replaced hard dependencies with clean interfaces ( DIP) We put lazy-loading in our own class, and resolving dependencies in our ( SRP ). Thus, reducing the number of dependencies to a minimum.
This very simple example actually helps a lot in my day-to-day development and makes it a lot easier.
Now, let's assume that all the dependencies are known at the compilation stage. Also suppose that the dependencies will not change while the application is running. In this case, we no longer need the Service Locator. We can override the CompileTimeInitialize method from LocationInterceptionAspect in order to resolve dependencies at compile time and save the type in some field. Then, while the application is running, you can use Activator.CreateInstance to create the desired object.

[Serializable]
public sealed class LoadDependencyAttribute : LocationInterceptionAspect
{
  private Type _type;

  public override bool CompileTimeValidate(PostSharp.Reflection.LocationInfo locationInfo)
  {
    _type = DependencyMap.GetConcreteType(locationInfo.LocationType);
    if(_type == null)
    {
      Message.Write(SeverityType.Error, "002",
                    "A concrete type was not found for {0}.{1}",
                    locationInfo.DeclaringType, locationInfo.Name);
      return false;
    }
    return true;
  }

  public override void OnGetValue(LocationInterceptionArgs args)
  {
    args.ProceedGetValue();
    if (args.Value == null)
    {
      form.LogListBox.Items.Add("Instantiating UserService");
      args.SetNewValue(Activator.CreateInstance(_type));
      args.ProceedGetValue();
    }
  }
}

* This source code was highlighted with Source Code Highlighter.


If you have not configured any type, then you will get an error at the compilation stage, and not at the application stage, which is very convenient. I hope that you are already starting to understand that PostSharp is a very powerful tool. Of course, I am not interested in using this method everywhere where it is necessary or not necessary. However, I am sure that it is useful in many circumstances.
Another option to improve the resulting aspect is to check at compile time that the aspect is correctly used. For example, it makes no sense to use the LoadDependency aspect for a field if that field is not an interface. Because it would mean that our dependencies are hard-coded and you need to change everything again! :) So let's add a couple of extra checks:

public override bool CompileTimeValidate(PostSharp.Reflection.LocationInfo locationInfo)
{
  if(!locationInfo.LocationType.IsInterface)
  {
    Message.Write(SeverityType.Error, "001",
            "LoadDependency can only be used on Interfaces in {0}.{1}",
            locationInfo.DeclaringType, locationInfo.Name);
    return false;
  }

  _type = DependencyMap.GetConcreteType(locationInfo.LocationType);
  if(_type == null)
  {
    Message.Write(SeverityType.Error, "002",
            "A concrete type was not found for {0}.{1}",
            locationInfo.DeclaringType, locationInfo.Name);
    return false;
  }
  return true;
}

* This source code was highlighted with Source Code Highlighter.


To get compilation errors, set the “LoadDependency” attribute for a field that has any type, but not an interface:



So, with a simple aspect coupled with an IoC container, we got rid of hard dependencies and boilerplate code and made our application easy to maintain, easy to testing and reading the source code. And now, just as importantly, our application fully follows SOLID principles. Now you save a lot of time, not only at the development and testing stages, but also at the support stage.

Other translations and links:

Also popular now: