2010-07-08 15 views
22

.NET 4.0 tiene una buena clase de utilidad llamada System.Lazy que hace la inicialización de objetos perezosos. Me gustaría usar esta clase para un proyecto de 3.5. Una vez vi una implementación en alguna parte de una respuesta de stackoverflow pero no puedo encontrarla más. ¿Alguien tiene una implementación alternativa de Lazy? No necesita todas las características de seguridad de subprocesos de la versión de Framework 4.0.Implementación de Lazy <T> para .NET 3.5

Actualizado:

respuestas contienen un no seguro para subprocesos y una versión segura hilo.

Respuesta

25

Aquí hay una implementación que yo uso.

/// <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(); 
    } 
} 
+0

Tengo dos problemas con esto: en primer lugar, es preferible' bloquear' un objeto privado que 'bloquear (esto)', ya que no se puede controlar quién de lo contrario, podría bloquear tu instancia 'Lazy'. En segundo lugar, no creo que hacer que 'isValueCreated' sea un campo' volátil' sirve para ningún propósito cuando ya está usando una sección crítica (¿me equivoco? Corrígeme si estoy equivocado). – Aaronaught

+0

Acepto que se usa volátil cuando no se usa el bloqueo. Desde MSDN: el modificador volátil generalmente se usa para un campo al que acceden varios subprocesos sin utilizar la instrucción de bloqueo para serializar el acceso. El uso del modificador volátil garantiza que un hilo recupere el valor más actualizado escrito por otro hilo. –

+0

Modifiqué la respuesta. –

10

Si no necesita seguridad de hilo, es bastante fácil armar uno con un método de fábrica. Yo uso una muy similar a la siguiente:

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; 
     } 
    } 
} 
+0

Cualquiera que pueda copiar esto: Puede ser fácil confundir el cierre con el inicializador aquí. ¡Asegúrate de capturar tus valores! –

+0

@Rex: ¿Quiere decir, si está inicializando la instancia 'Lazy ' de un bucle? ¿'System.Lazy' hace algo de magia para eludir los peligros normales de la captura? – Aaronaught

+0

un ciclo es un buen ejemplo. Dudo si el verdadero 'Lazy' es diferente, aunque no estoy seguro. Solo pensé en señalarlo porque veo a mucha gente meterse en problemas con este tipo de patrón. –

2

Un poco simplificar versión de

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; 
    } 
    } 
} 
+0

Requiere ctor predeterminado –

+4

@BC: Sí, que es lo que significa 'donde T: new()'. –

-1

algunos divertidos (pero no muy útil) las cosas se pueden añadir de aaron: coversion implícita del delegado:

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

y el uso

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

El compilador C# tiene algún problema al realizar esta conversión, por ejemplo, la asignación de la expresión lambda no funciona, pero es una cosa más que hace que sus colleguas piensen un poco :)

+0

lambdas pueden ser árboles de expresión o delegados, por lo que el compilador se niega a considerarlos como uno o el otro. La misma razón por la que no puedes poner una lambda en una var. Realmente molesto a veces ... –

+0

Tiene razón, pero este código no funciona ni siquiera con los métodos: ' public static int NewValue() { return 24 * 15; } public static Lazy V = NewValue; // Error de compilación, necesita el nuevo Func (NewValue) ' – STO