3

I would like to implement lazy loading on properties with PostSharp.

To make it short, instead of writing

SomeType _field = null;
private SomeType Field
{
    get
    {
        if (_field == null)
        {
            _field = LongOperation();
        }
        return _field;
    }
}

I would like to write

[LazyLoadAspect]
private object Field
{
    get
    {
        return LongOperation();
    }
}

So, I identify that I need to emit some code in the class to generate the backing field, as well as inside the getter method in order to implement the test.

With PostSharp, I was considering overriding CompileTimeInitialize, but I am missing the knowledge to get a handle over the compiled code.

EDIT: The question can be extended to any parameterless method like:

SomeType _lazyLoadedField = null;
SomeType LazyLoadableMethod ()
{
    if(_lazyLoadedField ==null)
    {
        // Long operations code...
        _lazyLoadedField = someType;
    }
    return _lazyLoadedField ;
}

would become

[LazyLoad]
SomeType LazyLoadableMethod ()
{
     // Long operations code...
     return someType;
}
remio
  • 1,242
  • 2
  • 15
  • 36

3 Answers3

6

After our comments, I think I know what you want now.

[Serializable]
    public class LazyLoadGetter : LocationInterceptionAspect, IInstanceScopedAspect
    {
        private object backing;

        public override void OnGetValue(LocationInterceptionArgs args)
        {
            if (backing == null)
            {
                args.ProceedGetValue();
                backing = args.Value;
            }

            args.Value = backing;
        }

        public object CreateInstance(AdviceArgs adviceArgs)
        {
            return this.MemberwiseClone();
        }

        public void RuntimeInitializeInstance()
        {

        }
    }

Test code

public class test
    {
        [LazyLoadGetter]
        public int MyProperty { get { return LongOperation(); } }
    }
Dustin Davis
  • 14,482
  • 13
  • 63
  • 119
  • Thanfs Dustin. I had already read your article before, but I've been confused because I understood it as very IoC-specific, and could not see where a call like `LongOperation` would take place. I'll give it a closer look with new eyes. – remio Mar 02 '12 at 18:19
  • If you are always going to call the same method then replace the IoC stuff with your call to LongOperation. The issue comes when you want to do different things, you'll need to create different aspects or use somethign more generic like a service locator or factory. – Dustin Davis Mar 02 '12 at 18:37
  • Ok, thank you, this is what I suspected. Indeed, I was looking for something generic. As presented in my question, I want to implement my property regardless of the aspect, and then, decide, probably later, to lazy load its value. Exactly like .Net4 Lazy, as nemesv suggested, but in an AOP way. – remio Mar 02 '12 at 20:18
  • I'm sorry, I have tested your suggestion, and this does not answer my need. The main issue is that, the first thing your code does, each time, is to call `args.ProceedGetValue();` which invokes the code of the getter. And precisely, what I want to avoid is to call the getter more than once! This is why I talked about generating the backing field in order to fill it with the computed value at the frst access, and then just return it. Anyway, thanks for your time. – remio Mar 03 '12 at 21:31
  • Why would you want to avoid makign a call to the getter, what is the point of having a property then? – Dustin Davis Mar 04 '12 at 07:54
  • Maybe my question was not clear enough. I could have been less specific to avoid focus on properties. Finally, the getter of a property is just a parameterless method, and my question could be extended to "How can I implement LazyLoading of a method without caring about LazyLoad thanks to PostSharp?" I don't want to avoid calling the getter, I want to avoid calling it more than once and return the previously computed result instead in further calls. – remio Mar 05 '12 at 10:30
  • @remio You don't need lazyload or postsharp for that. You still need a GETTER to get the value of the backing field but what you want to do is in the getter, check if the field is in an unintialized state (maybe null) if it is, then set it's value by calling LongOperation, then continue like normal. Next time the getter is invoked, the backing field is now initialized so it doesn't have to run LongOperation again. Simple. You have to have a getter to get a value from the property. You're confusing the terms I think. – Dustin Davis Mar 05 '12 at 17:09
  • We have clearly a misunderstanding. What you suggest me to implement is exactly what I want to avoid doing thanks to PostSharp! I want to avoid declaring the backing fierld, and avoid checking its uninitialized state. I just want to implement my getter without thinking of a backing field. Then, if I feel that LazyLoading is interesting for this getter, I just want to add `[LazyLoadAspect]` on it. Then, this will (would) automagically generate a backing field and a check on its initialized state. – remio Mar 08 '12 at 15:35
  • @remio you ALWAYS have a backing field even when you don't explicitly declare one, it gets created in the background by the compiler. What you want to do is exactly what I suggested the first time with the aspect. You will ALWAYS need to check the backing field, that is where the value is stored. – Dustin Davis Mar 08 '12 at 15:59
  • I don't follow you on this point. If your property is like `public int Foo { get { return 5; } }`, there is no backing field generated (I just checked the IL code with reflector). And it is the same if I replace `5` with `LongOperation();`. My mistake was to focus on properties getters while my issue is just the same for a simple parameterless method as I stated in my latest edit for my original question. How can I implement a LazyLoaded value without explicitely declare a backing field and an initialized state on it? Anyway; thanks for your followup :) – remio Mar 08 '12 at 19:40
  • @remio I think I get it now, you want caching, not lazy loading. See my updated answer. – Dustin Davis Mar 08 '12 at 21:20
  • I have two more questions: 1. You wrote that I " want caching, not lazy loading". What do you mean? For me, LazyLoading is a specific kind of caching. What is your definition for LazyLoading? 2. Can you provide me pointers that would help me to understand the purpose of implementing `IInstanceScopedAspect` (or tell me that I should override my lazyness and check PostSharp documentation). Thanks again. – remio Mar 09 '12 at 11:45
  • 1
    @remio Lazy loading is the act of deferring an operation (usually because it's long running) until the resource is actually requested. The purpose is to wait until something is actually needed before spending the time to initialize it. If the resource is never requested then it doesn't get initialized. Caching is the storage of the result from an operation. The purpose is to only run the operation once (especially if it's long running). Any subsequent requests will returned the stored value, by passing the actual operation. – Dustin Davis Mar 09 '12 at 16:39
  • 1
    @remio technically, the code you wish to write is lazy loaded, it isn't initialized until it's requested. But that operation will be called every time the request is made which is what you wanted to avoid, so you need caching to store the result and avoid the operation in subsequent requests. Very subtle difference. – Dustin Davis Mar 09 '12 at 16:44
  • @remio aspects are scoped statically per type by default so a single instance of the aspect to service all instances of your class. Instance 2 would overwrite what value stored for Instance 1. Instance scoped aspects are 1:1 (one aspect instance to service one class instance). Plenty of good material here http://programmersunlimited.wordpress.com/postsharp-principals/ – Dustin Davis Mar 09 '12 at 16:47
1

Thanks to DustinDavis's answer and comments, I could work on my own implementation, and I just wanted here to share it to help other people.

The main differences from the original answer are:

  • Implement the suggested "only run the operation once" (purpose of the lock)
  • Made the initialization status of the backing field more reliable by passing this responsibility to a boolean.

Here is the code:

[Serializable]
public class LazyLoadAttribute : LocationInterceptionAspect, IInstanceScopedAspect
{
    // Concurrent accesses management
    private readonly object _locker = new object();

    // the backing field where the loaded value is stored the first time.
    private object _backingField;

    // More reliable than checking _backingField for null as the result of the loading could be null.
    private bool _hasBeenLoaded = false;

    public override void OnGetValue(LocationInterceptionArgs args)
    {
        if (_hasBeenLoaded)
        {
            // Job already done
            args.Value = _backingField;
            return;
        }

        lock (_locker)
        {
            // Once the lock passed, we must check if the aspect has been loaded meanwhile or not.
            if (_hasBeenLoaded)
            {
                args.Value = _backingField;
                return;
            }

            // First call to the getter => need to load it.
            args.ProceedGetValue();

            // Indicate that we Loaded it
            _hasBeenLoaded = true;

            // store the result.
            _backingField = args.Value;
        }
    }

    public object CreateInstance(AdviceArgs adviceArgs)
    {
        return MemberwiseClone();
    }

    public void RuntimeInitializeInstance() { }

}
Community
  • 1
  • 1
remio
  • 1,242
  • 2
  • 15
  • 36
0

I think the requirement cannot be accurately described as 'lazy loading', but is a special case of a more general caching aspect with in-AppDomain storage but without eviction. A general caching aspect would be able to handle method parameters.

Gael Fraiteur
  • 6,759
  • 2
  • 24
  • 31