Implementation of Lazy<T> for .NET 3.5
Asked Answered
A

4

23

.NET 4.0 has a nice utility class called System.Lazy that does lazy object initialization. I would like to use this class for a 3.5 project. One time I saw an implementation somewhere in a stackoverflow answer but I can't find it anymore. Does someone have an alternative implementation of Lazy? It doesn't need all the thread safety features of the framework 4.0 version.

Updated:

Answers contain a non thread safe and a thread safe version.

Amorino answered 8/7, 2010 at 20:14 Comment(0)
C
27

Here is an implementation that I use.

/// <summary>
/// Provides support for lazy initialization.
/// </summary>
/// <typeparam name="T">Specifies the type of object that is being lazily initialized.</typeparam>
public sealed class Lazy<T>
{
    private readonly object padlock = new object();
    private readonly Func<T> createValue;
    private bool isValueCreated;
    private T value;

    /// <summary>
    /// Gets the lazily initialized value of the current Lazy{T} instance.
    /// </summary>
    public T Value
    {
        get
        {
            if (!isValueCreated)
            {
                lock (padlock)
                {
                    if (!isValueCreated)
                    {
                        value = createValue();
                        isValueCreated = true;
                    }
                }
            }
            return value;
        }
    }

    /// <summary>
    /// Gets a value that indicates whether a value has been created for this Lazy{T} instance.
    /// </summary>
    public bool IsValueCreated
    {
        get
        {
            lock (padlock)
            {
                return isValueCreated;
            }
        }
    }


    /// <summary>
    /// Initializes a new instance of the Lazy{T} class.
    /// </summary>
    /// <param name="createValue">The delegate that produces the value when it is needed.</param>
    public Lazy(Func<T> createValue)
    {
        if (createValue == null) throw new ArgumentNullException("createValue");

        this.createValue = createValue;
    }


    /// <summary>
    /// Creates and returns a string representation of the Lazy{T}.Value.
    /// </summary>
    /// <returns>The string representation of the Lazy{T}.Value property.</returns>
    public override string ToString()
    {
        return Value.ToString();
    }
}
Congeries answered 8/7, 2010 at 20:32 Comment(4)
Two problems I have with this: First of all, it's preferable to lock a private object than to lock (this), since you can't control who else might lock on your Lazy instance. Second, I don't think making isValueCreated a volatile field serves any purpose when you're already using a critical section (does it? Correct me if I'm wrong).Ardisardisj
I agree volatile is used when locking is not used. From MSDN: The volatile modifier is usually used for a field that is accessed by multiple threads without using the lock statement to serialize access. Using the volatile modifier ensures that one thread retrieves the most up-to-date value written by another thread.Amorino
@Aaronaught, @BC - I know this may sound stupid but imagine a situation where isValueCreated is read and written nearly at the same time. If isValueCreated is volatile then the extra lock will not need to be taken because the latest value was read. One simple lock may not seem like much but I did it for completeness sake. As for the this lock, I do see your point but it just never was an issue I ran into while using this class.Congeries
The code doesn't seems to be entirely thread-safe yet. It may be better to use a memory barrier as suggested in Wikipedia: en.wikipedia.org/wiki/…Rodenticide
A
11

If you don't need thread-safety, it's pretty easy to put one together with a factory method. I use one very similar to the following:

public class Lazy<T>
{
    private readonly Func<T> initializer;
    private bool isValueCreated;
    private T value;

    public Lazy(Func<T> initializer)
    {
        if (initializer == null)
            throw new ArgumentNullException("initializer");
        this.initializer = initializer;
    }

    public bool IsValueCreated
    {
        get { return isValueCreated; }
    }

    public T Value
    {
        get
        {
            if (!isValueCreated)
            {
                value = initializer();
                isValueCreated = true;
            }
            return value;
        }
    }
}
Ardisardisj answered 8/7, 2010 at 20:16 Comment(4)
Anyone who might copy this: It can be easy to have closure confusion with the initializer here. Make sure to capture your values!Pleopod
@Rex: Do you mean, if you're initializing the Lazy<T> instance from a loop? Does System.Lazy do some magic to sidestep the normal dangers of capturing?Ardisardisj
a loop is a good example. I doubt if the real Lazy is any different, though I am not sure. Just thought I'd point it out because I see a lot of people get themselves in trouble with this kind of pattern.Pleopod
@Rex: Alrighty, just wanted to make sure you were referring to a usage scenario and not a design flaw that I failed to recognize.Ardisardisj
C
2

A somewhat simplify version of aaron's

public class Lazy<T> where T : new()
{ 
  private T value; 

  public bool IsValueCreated { get; private set;}

  public T Value 
  { 
    get 
    { 
        if (!IsValueCreated) 
        { 
            value = new T();
            IsValueCreated = true; 
        } 
        return value; 
    } 
  } 
} 
Cure answered 8/7, 2010 at 20:26 Comment(1)
@BC : Yes, which is what the ` where T : new() ` means.Cure
C
-1

Some funny (but not very usable) stuff can be added: implicit coversion from delegate:

public static implicit operator Lazy<T>(Func<T> initializer)
{
    return new Lazy<T>(initializer);
}  

And usage

private static Lazy<int> Value = new Func<int>(() => 24 * 22);

C# compiler have some problem with performing this conversion, for example assigning lambda expression does not work, but it is one more thing causes your colleguas to think a bit :)

Confrere answered 8/7, 2010 at 20:29 Comment(2)
lambdas can either be expression trees or delegates so the compiler refuse to consider them to be one or the other. same reason why you can't put a lambda in a var. Really annoying sometimes...Urdar
You are right, but this code does not work even with methods: ` public static int NewValue() { return 24 * 15; } public static Lazy<int> V = NewValue; // Compilation Error, needs new Func<int>(NewValue)`Confrere

© 2022 - 2024 — McMap. All rights reserved.