One Time Property - Kind of Lazy

Topics: C# Language Design
Apr 4, 2014 at 7:25 PM
Edited Apr 4, 2014 at 7:28 PM
First of, I'm complete new here and I'm also not a C# Expert as most of you here or even know if here is the there right place for it. But there's one thing that I do very often and I believe it would be cool to be a language feature as automatic properties are.
    private Int32? _foo = null;
    public Int32 Foo
    {
        get
        {
            if (_foo == null)
            {
                _foo = SuperHeavyMath();
            }
            return _foo ?? 0;
        }
    }
It would be cool to write it as:
    public Int32 Foo
    {
        first get
        {
            return SuperHeavyMath();
        }
    }
Not saying it should be done like that, but you get the idea.

I verified in the past if it were already implemented into the language but what I got was a way to create Code Snippets.

Thank you. And I'm sorry if I got to the wrong place.

ps: I use it a lot in ASP.NET
Developer
Apr 4, 2014 at 8:37 PM
Edited Apr 4, 2014 at 8:37 PM
We use this in Roslyn a lot so I've thought of it too. It's actually almost equivalent to lazy vals in Scala.

I do have a couple issues with the proposal as-written, though.

First, the code you've written there is not thread safe.

If we wanted to make it thread safe, we would have to choose a thread safety mechanism. Scala uses double-checked locking, but in Roslyn we always use InterlockedExchange for performance.

I imagine some people would want to make the exact opposite choice for the same reason. I think we need more information on if one usage would clearly dwarf another.

Second, I don't think it's a good idea to reserve the "first" keyword, so I would play with the syntax a bit.
Developer
Apr 4, 2014 at 8:45 PM
Why don't you use Lazy<T>?

public int Foo { get { return lazyFoo.Value; } }
private readonly Lazy<int> lazyFoo = new Lazy<int>(SuperHeavyMath); }
Developer
Apr 4, 2014 at 8:59 PM
Edited Apr 4, 2014 at 9:01 PM
It is possible to do something similar today with System.Lazy.
    private Lazy<Int32> _foo = new Lazy<Int32>(() => SuperHeavyMath());

    public Int32 Foo
    {
        get { return _foo.Value;}
    }

    private static int SuperHeavyMath() { return 0;}
This will only compute the value once and store it in the property "Value." You can even make it thread safe by passing in the optional argument.
    private Lazy<Int32> _foo = new Lazy<Int32>(() => SuperHeavyMath(), isThreadSafe: true);

    public Int32 Foo
    {
        get { return _foo.Value;}
    }
We implemented something similar ourselves for when the value is computed asynchronously called AsyncLazy<T>
Coordinator
Apr 4, 2014 at 9:35 PM
Yes. The question about supporting lazy initialization in the language comes up very often.

Other concerns are:
1) the reentrancy policy:
What should happen if SuperHeavyMath accesses Foo recursively (intentionally or because of a bug) ? Should the code just stackoverflow or return 0, or throw some special debugging-friendly exception indicating that reentrancy happened? Or should we have a syntactical knob to configure this behavior?

2) optimistic/exclusive execution:
Assuming the thread safety is addressed, what happens if two threads access Foo? Do we execute SuperHeavyMath optimistically twice and then the first thread to report the result wins, or do we block the second thread while another thread does the work.
In the Roslyn codebase we often use the optimistic pattern. That requires the worker method to be relatively fast and to not have any observable sideeffects (so you can call it multiple times). The major advantage is that you will not get into a deadlock if two lazy properties call each other at the same time on different threads.
I can imagine that in different circumstances you might want different behavior. Another knob needed?

3) what happens if SuperHeavyMath throws an exception? Do we allow subsequent callers to reattempt the computation or should we store the exception and pass it somehow to all subsequent callers?

However attractive it is to provide lazy initialization support in the language, it is not an easy thing to do when you get into details. The main problem is really that lazy initialization is not a single pattern. It is actually a family of patterns that behave differently.

In theory we could just favor one particular scenario - that would seem too limiting for a language feature. We could also generalize the feature by providing ways to configure the behavior, but after adding two or three "knobs" the feature invariably becomes too unwieldy to be practical.
Marked as answer by angocke on 4/5/2014 at 12:39 PM
Apr 7, 2014 at 2:40 AM
My 2 cents:
the reentrancy policy
Ideally it'd throw a specific exception, and I don't imagine it'd be much harder to implement (considering you already must do some locking/thread checking mechanisms)
optimistic/exclusive execution:
The optimistic approach seems like the safest to me here. The spec would just say that it'd only return a pre-computed value if one has been previously computed. If one is in process, it will attempt the computation again. There will need to be some understanding that the function may be called more than once, and hopefully there aren't side effects within the function that would cause the computations to return separate values.
what happens if SuperHeavyMath throws an exception?
This is really just a question of whether we consider SuperHeavyMath to be a pure function. If it is a pure function, then storing the exception would be the route to take. If it's not a pure function, then perhaps the conditions have changed and the method should be retried.

A lot of the issues come down to whether to allow non-pure functions, but allowing only pure functions would make this very limited, because I can imagine this being used to load in settings, or something similar, which by it's very nature has side effects.

Another thing to consider would be allowing this on functions rather than just properties. If it's called with the same parameters as a time before it could recall the previous result, and this would make a lot of algorithms which require memoization MUCH simpler to program. However this would also introduce a WHOLE bunch more problems (how many results to remember, when to remove old ones, which to remove etc), so it's probably not worth it.
Apr 10, 2014 at 9:14 PM
Edited Apr 14, 2014 at 5:47 PM
I really like this. In current .NET i find myself occasionally using lazy to accomplish this (feels kind of dirty). The past versions I used this (which is a short hand of the original code sample).

get { return _foo ?? (_foo = SuperHeavyMath()); }

I don't quite follow why you would need the zero manually instead of SuperHeavyMath() returning 0, but you could still one line it

get { return _foo ?? (_foo = (SuperHeavyMath() ?? 0)); }