Own implementation of Lazy object The problem with the original Lazy in C# is that you have to put the initialization in the constructor if you want to refer to this.

For me that is 95% of the cases. Meaning that I have to put half of my logic concerning the lazy property in the constructor, and having more boilerplate code.

See this question: https://stackoverflow.com/questions/53271662/concise-way-to-writy-lazy-loaded-properties-in-c-sharp/53316998#53316998

Now I made a new version where I actually move the initialization part to the 'Get' moment. This saves boilerplate code and I can group everything about this property together.

But:


is the current implementation 'safe'?
are there any important performance considerations vs the original one?
is there anything else I'm missing that I should be concerned about in a high traffic application?


Class:

public class MyLazy
{
    private object  _Value;
    private bool    _Loaded;
    private object  _Lock = new object();


    public T Get&lt;T&gt;(Func&lt;T&gt; create)
    {
        if ( _Loaded )
            return (T)_Value;

        lock (_Lock)
        {
            if ( _Loaded ) // double checked lock
                return (T)_Value;

            _Value   = create();
            _Loaded = true;
        }

        return (T)_Value;
    } 


    public void Invalidate()
    {
        lock ( _Lock )
            _Loaded = false;
    }
}


Use:

MyLazy _SubEvents = new MyLazy();
public SubEvents SubEvents =&gt; _SubEvents.Get(() =&gt;
{
    // [....]
    return x;
});


Or like:

MyLazy _SubEvents = new MyLazy();
public SubEvents SubEvents =&gt; _SubEvents.Get(LoadSubEvents);

public void LoadSubEvents()
{
    // [....]
    return x;
}
is the current implementation 'safe'?


Absolutely not. The fact that you had to ask this question indicates that you do not understand enough about threading to build your own mechanisms like this. You need to have a deep and thorough understanding of the memory model to build these mechanisms. That is why you should always rely on the mechanisms provided for you in the framework, that were written by experts..

Why is it unsafe? Consider the following scenario. We have two threads, A and B. _Value is null and _Loaded is false.


We're on thread A.
The memory location of _Value is loaded into the processor cache for the CPU that thread A is affinitized to. It is null.
We switch to thread B.
Thread B reads _Loaded as false, takes the lock, checks _Loaded again, calls create, assigns _Value and _Loaded and leaves the lock.
We switch back to thread A.
_Loaded is now true, so thread A returns _Value from the processor cache, which is null.


Thread A is not required to invalidate the cache because thread A never takes a lock.!

Now, I made an argument here from processor caches. This is the wrong argument to make in general. Rather, what you must do when trying to build a new threading mechanism like this is to not reason about any specific processor architecture, but rather to reason about the abstract memory model of the C# language.  C# permits reads and writes to move forwards and backwards in time in multithreaded programs.  Any time travel that is not explicitly forbidden by the C# specification must be considered to be possible.  Your task is to then write code that is correct for any possible combination of movements of reads and writes in time regardless of whether they are really possible on a specific processor or not.  

Note that in particular the C# specification does not require that all threads observe a consistent set of write and read re-orderings. It is perfectly legal and possible for two threads to disagree on how a read was re-ordered with respect to a write.

If writing correct programs in a world where all reads and writes can be moved around in time sounds hard, that's because it is. I am not competent to do this work, and I do not attempt to. I leave it to experts.


  are there any important performance considerations vs the original one?


Only you can answer that question.  Answer performance questions by gathering real-world empirical data.

However, I can say a few things about this problem in general.

The first is: double-checked locking is intended to avoid the cost of the lock. Let's examine the assumptions underlying that intention.  The assumption is that the cost of taking the lock is too high on the uncontended path.  Is that assumption warranted? What is the cost of taking an uncontended lock? Did you measure it? Did you compare it against the cost of the lock-avoiding check?  (Since the lock-avoiding check code is wrong, testing it for performance is not actually meaningful since we can always write faster code if we don't care about correctness, but still, we need to know whether this intervention is an improvement.)  And most importantly, is the cost of taking an uncontended lock relevant to the consumer of this code?  Because they are the stakeholder whose opinions are relevant; what do they say about the cost of an uncontended lock?

Let's suppose that the cost of an uncontended lock is relevant. Then surely the cost of a contended lock is enormously relevant.  You've built a mechanism that potentially contends a lot of threads!  What are the alternatives that you considered here?  For example, you could avoid the lock altogether by deciding that it is OK for the create function to be called on multiple threads -- perhaps we know that it is cheap and idempotent.  Those threads can then race to their heart's content to initialize the field, and we can use an interlocked exchange to ensure that we get a consistent value. That avoids the cost of the lock altogether, but it creates a different kind of cost, and puts a requirement on the caller to pass an idempotent creator.

Let's consider other aspects of your solution with respect to performance. You allocate the lock object regardless of whether you ever take the lock, and you keep it forever.  What's the burden on the garbage collector? What is the impact on collection pressure? These things are all deeply relevant to performance.  Again, remember, the assumption here is that we are so worried about the couple of nanoseconds it takes to enter and leave an uncontended lock that we're willing to write a double checked lock.  If those nanoseconds are relevant then surely the milliseconds it takes to do an extra collection are incredibly relevant!


  is there anything else I'm missing that I should be concerned about in a high traffic application?


I don't know how to answer that question.