lazy example ejemplos ejemplo code c# code-review design-patterns lazy-loading

c# - example - Carga lenta y patrones de ejecución perezosos



singleton c# ejemplo (3)

El método de prueba es un poco tonto porque obviamente la referencia de myLazyObject no será nula: acaba de llamar "nuevo". En realidad, no estás probando ninguna conversión.

No creo que se necesiten el operador de conversión, el código hash, la comprobación de igualdad o IsNullOrDefault. La lógica de GetValue () es necesaria, pero no tendría una propiedad Value y un método GetValue. Solo tiene la propiedad, y ponga la lógica allí.

No expondría a los delegados, particularmente no como una matriz en bruto, lo que significa que el mundo exterior puede mutar la matriz. De hecho, no tomaría una matriz en primer lugar, o al menos no la almacenaría como una matriz. Lo almacenaba como un solo delegado: son multidifusión, después de todo. Eso le dará el valor de retorno del último delegado en la cadena en lugar del primero, pero espero que no sea un problema en el uso real de todos modos. (Espero que sea bastante extraño tener más de un delegado especificado).

Yo haría la clase sellada, posiblemente implementando una interfaz (por ejemplo, IProvider<T> ) que solo tiene la propiedad Value . De esta forma, para otros clientes que solo quieran proporcionar un valor, podría haber FixedProvider<T> que solo tomó una instancia de T en el constructor.

Piense si necesita que esto sea seguro o no, también.

¿Eso ayuda mucho?

EDITAR: Una cosa más: hacer el campo delegado de solo lectura. No puede hacer eso mientras sea una propiedad automática, pero es fácil una vez que no lo expone :) También agruparía todos los campos / autopropiedades juntos en la parte superior de la clase, por lo que es fácil ver cuál es el estado involucrado es.

Me gustaría obtener algunos comentarios sobre una clase que escribí para encapsular el patrón de carga lenta. Por lo general, cuando hago lazy-loading, se ve algo como esto:

private SomeClass _someProperty; public SomeClass SomeProperty { get { if(this._someProperty == null) { // run code to populate/instantiate _someProperty } return this._someProperty; } }

La forma en que funciona la clase es bastante básica: acepta un parámetro de tipo genérico y uno o más delegados de método utilizando Func <T>. Si se especifica más de un delegado, se utiliza el primero para devolver un valor no predeterminado (esta característica particular puede no ser útil para todos, pero fue necesaria en el dominio en el que la estaba usando).

Aquí hay un breve extracto que creo que muestra más o menos lo que hace la clase:

/// <summary> /// A container for an object of type <typeparamref name="T"/> whose value is not known until the <see cref="Value" /> property is accessed. /// </summary> /// <typeparam name="T">The type of the underlying object.</typeparam> public class Lazy<T> { /// <summary> /// A value representing whether the delegates have been invoked. /// </summary> private Boolean _wereValueDelegatesInvoked; /// <summary> /// Initializes a new instance of the <see cref="Lazy{T}" /> class with the specified <paramref name="valueDelegates"/>. /// </summary> /// <param name="valueDelegates">A list of delegates that can potentially return the value of the underlying object.</param> public Lazy(params Func<T>[] valueDelegates) { this.ValueDelegates = valueDelegates; } /// <summary> /// Gets the delegate that returns the value of the underlying object. /// </summary> public IEnumerable<Func<T>> ValueDelegates { get; protected set; } private T _value; /// <summary> /// Gets the value of the underlying object. If multiple delegates are specified, the first delegate to return a non-default value will be used. /// </summary> public T Value { get { if (!this._wereValueDelegatesInvoked) { this._value = this.GetValue(); } return this._value; } } /// <summary> /// Return the value of the underlying object. If multiple delegates are specified, the first delegate to return a non-default value will be used. /// </summary> /// <returns>The value of the underlying object.</returns> protected T GetValue() { T value = default(T); if (this.ValueDelegates != null) { foreach (Func<T> valueDelegate in this.ValueDelegates) { value = valueDelegate.Invoke(); if (!Lazy<T>.IsNullOrDefault(value)) break; } } this._wereValueDelegatesInvoked = true; return value; } }

... y para aquellos para quienes esto no evocará una respuesta de "tl; dr", aquí está la clase en su totalidad:

/// <summary> /// A container for an object of type <typeparamref name="T"/> whose value is not known until the <see cref="Value" /> property is accessed. /// </summary> /// <typeparam name="T">The type of the underlying object.</typeparam> public class Lazy<T> { /// <summary> /// A value representing whether the delegates have been invoked. /// </summary> private Boolean _wereValueDelegatesInvoked; /// <summary> /// Initializes a new instance of the <see cref="Lazy{T}" /> class with the specified <paramref name="valueDelegate"/>. /// </summary> /// <param name="valueDelegate">A delegate that returns the value of the underlying object.</param> public Lazy(Func<T> valueDelegate) { this.ValueDelegates = new Func<T>[] { valueDelegate }; } /// <summary> /// Initializes a new instance of the <see cref="Lazy{T}" /> class with the specified <paramref name="valueDelegates"/>. /// </summary> /// <param name="valueDelegates">A list of delegates that can potentially return the value of the underlying object.</param> public Lazy(IEnumerable<Func<T>> valueDelegates) { this.ValueDelegates = valueDelegates; } /// <summary> /// Initializes a new instance of the <see cref="Lazy{T}" /> class with the specified <paramref name="valueDelegates"/>. /// </summary> /// <param name="valueDelegates">A list of delegates that can potentially return the value of the underlying object.</param> public Lazy(params Func<T>[] valueDelegates) { this.ValueDelegates = valueDelegates; } public Lazy(T value) { this._value = value; this._wereValueDelegatesInvoked = true; } /// <summary> /// Gets the delegate that returns the value of the underlying object. /// </summary> public IEnumerable<Func<T>> ValueDelegates { get; protected set; } private T _value; /// <summary> /// Gets the value of the underlying object. If multiple delegates are specified, the first delegate to return a non-default value will be used. /// </summary> public T Value { get { if (!this._wereValueDelegatesInvoked) { this._value = this.GetValue(); } return this._value; } } /// <summary> /// Return the value of the underlying object. If multiple delegates are specified, the first delegate to return a non-default value will be used. /// </summary> /// <returns>The value of the underlying object.</returns> protected T GetValue() { T value = default(T); if (this.ValueDelegates != null) { foreach (Func<T> valueDelegate in this.ValueDelegates) { value = valueDelegate.Invoke(); if (!Lazy<T>.IsNullOrDefault(value)) break; } } this._wereValueDelegatesInvoked = true; return value; } private static Boolean IsNullOrDefault(T value) { if (value == null) return true; else if (value.Equals(default(T))) return true; else return false; } public override Boolean Equals(Object obj) { if (this.Value == null) return (obj == null); return this.Value.Equals(obj); } public override Int32 GetHashCode() { if (this.Value == null) return base.GetHashCode(); else return this.Value.GetHashCode(); } /// <summary> /// Returns the value of the <see cref="Lazy{T}" />. /// </summary> /// <param name="value">A <see cref="Lazy{T}" /> object.</param> /// <returns>The value of the <see cref="Value" /> property for the <paramref name="value" /> parameter.</returns> public static implicit operator T(Lazy<T> value) { return value.Value; } /// <summary> /// Returns a <see cref="String" /> that represents the current <see cref="Lazy{T}" />. /// </summary> /// <returns>A <see cref="String" /> that represents the current <see cref="Lazy{T}" />.</returns> public override String ToString() { if (Lazy<T>.IsNullOrDefault(this.Value)) return null; else return this.Value.ToString(); } }

La clase se usa así:

[TestMethod] public void ABadFakeWorkExample() { var myLazyObject = new Lazy<Object>((() => { Thread.Sleep(5000); return new Object(); })); Assert.IsNotNull(myLazyObject); }

Y un ejemplo usando múltiples delegados:

[TestMethod] public void LazyUsesFirstNonDefaultResult() { Lazy<DateTime> lazy = new Lazy<DateTime>( (() => { return default(DateTime); }), (() => { return DateTime.Now; }), (() => { return default(DateTime); })); Assert.AreNotEqual(default(DateTime), lazy.Value, "D''OH!"); }

Así que esto es lo que me gustaría saber:

  • ¿Es esto exagerado? Quiero decir que es bastante fácil crear un fragmento para el primer patrón que publiqué, ¿verdad? Y si lo es, ¿es tanto que esta clase no debería usarse?
  • Realicé algunas pruebas preliminares en ANTS, y esto parece ser bastante ligero, pero ¿hay algún problema de rendimiento que me preocupe?
  • ¿Algo que se pueda agregar?
  • ¿Algo que deba ser quitado?

Nota no relacionada con la pregunta real: Basado en los comentarios que recibí de esta pregunta , dejo la casilla del wiki sin marcar ... Comente si realmente cree que esto debería ser wikificado.


Un par de puntos:

  • No sé si permitiría que varios delegados: si hubiera un caso en el que podría necesitar más de una función de carga, ¡podría envolverlo en un solo func!

  • Puede valer la pena simplemente exponer el valor en sí mismo en lugar de implementar el código hash / comprobación de igualdad, etc.

Editar: Solo pensé en una consideración: ¿qué tan fácil / difícil va a hacer probar la clase contenedora?


Esto se ve muy similar a la estructura LazyInit <> en el paquete Parallel Extensions que se incluirá en .NET 4.0. Las principales diferencias son:

  • La implementación de Microsoft no permite múltiples delegados de inicialización.
  • Usan Interlocked.CompareExchange y algunos otros trucos para garantizar la seguridad del hilo.
  • LazyInit es una estructura, en lugar de una clase.

El último punto se expande un poco. Al hacer de LazyInit una estructura en lugar de una clase puede provocar errores si no tiene cuidado ( no almacene LazyInit en una variable de solo lectura ), hay un beneficio de rendimiento potencial bastante grande al hacerlo de esta manera. En la implementación de MS, si no proporciona un delegado de inicialización, intentará crear la clase dada llamando a un constructor de cero argumentos en la clase. Como LazyInit en sí mismo es una estructura, no necesita ser instanciada.

private LazyInit<Foo> fooHolder;

Si algún código llama a fooHolder.Value, se creará una sola instancia de la clase Foo. Sin embargo, en este ejemplo si nunca se llama a fooHolder.Value, fooHolder nunca se crea una instancia, no se ejecuta código y no se asigna memoria, según tengo entendido. En los casos en los que tenga muchos LazyInits que podrían no ser todos usados, esto puede representar un ahorro significativo.