Pregunta

A menudo me encuentro escribiendo una propiedad que se evalúa perezosamente. Algo así como:

if (backingField == null) 
  backingField = SomeOperation();
return backingField;

No es mucho código, pero se repite mucho si tiene muchas propiedades.

Estoy pensando en definir una clase llamada LazyProperty:

public class LazyProperty<T>
    {
    private readonly Func<T> getter;

    public LazyProperty(Func<T> getter)
    {
        this.getter = getter;
    }

    private bool loaded = false;
    private T propertyValue;

    public T Value
    {
        get
        {
            if (!loaded)
            {
                propertyValue = getter();
                loaded = true;
            }
            return propertyValue;
        }
    }

    public static implicit operator T(LazyProperty<T> rhs)
    {
        return rhs.Value;
    }
}

Esto me permitiría inicializar un campo como este:

first = new LazyProperty<HeavyObject>(() => new HeavyObject { MyProperty = Value });

Y luego el cuerpo de la propiedad podría reducirse a:

public HeavyObject First { get { return first; } }

Esto sería utilizado por la mayoría de la compañía, ya que iría a una biblioteca de clase común compartida por la mayoría de nuestros productos.

No puedo decidir si es una buena idea o no. Creo que las soluciones tienen algunas ventajas, como:

  • Menos código
  • Código más bonito

En el lado negativo, sería más difícil mirar el código y determinar exactamente qué sucede, especialmente si un desarrollador no está familiarizado con la clase LazyProperty.

¿Qué te parece? ¿Es una buena idea o debería abandonarla? Además, ¿es una buena idea el operador implícito, o preferiría usar la propiedad Value explícitamente si debería usar esta clase?

Las opiniones y sugerencias son bienvenidas :-)

¿Fue útil?

Solución

Solo para ser demasiado pedante:

Su solución propuesta para evitar repetir el código:

private LazyProperty<HeavyObject> first = 
  new LazyProperty<HeavyObject>(() => new HeavyObject { MyProperty = Value });
public HeavyObject First { 
  get { 
    return first; 
  } 
}

En realidad, hay más caracteres que el código que no quería repetir:

private HeavyObject first;
public HeavyObject First { 
  get {
    if (first == null) first = new HeavyObject { MyProperty = Value };
    return first;
  }
}

Aparte de eso, creo que el reparto implícito hizo que el código fuera muy difícil de entender. No habría adivinado que un método que simplemente regresa primero, en realidad termina creando un HeavyObject. Al menos habría eliminado la conversión implícita y devuelto primero. Valor de la propiedad.

Otros consejos

No lo hagas en absoluto.

Generalmente, usar este tipo de propiedades perezosas inicializadas es una opción de diseño válida en un caso: cuando SomeOperation (); es una operación costosa (en términos de E / S, como cuando requiere un DB golpe, o computacionalmente) Y cuando esté seguro, a menudo NO necesitará acceder a él.

Dicho esto, de forma predeterminada, debe realizar una inicialización ansiosa, y cuando el generador de perfiles dice que es su cuello de botella, luego cámbielo a una inicialización lenta.

Si siente la necesidad de crear ese tipo de abstracción, es un olor.

Seguramente al menos desearía que el LazyPropery < T > sea un tipo de valor, de lo contrario, ha agregado memoria y presión GC para cada " cargado con pereza " propiedad en su sistema.

Además, ¿qué pasa con los escenarios de subprocesos múltiples? Considere dos hilos que solicitan la propiedad al mismo tiempo. Sin bloqueo, podría crear potencialmente dos instancias de la propiedad subyacente. Para evitar bloquearse en el caso común, querrá hacer un bloqueo doblemente verificado.

Prefiero el primer código, porque a) es un patrón tan común con propiedades que lo entiendo de inmediato, yb) el punto que planteaste: que no hay magia oculta que tengas que buscar para entender dónde y cuando se obtiene el valor.

Me gusta la idea porque es mucho menos código y más elegante, pero estaría muy preocupado por el hecho de que se hace difícil verlo y decir lo que está sucediendo. La única forma en que lo consideraría es tener una convención para las variables establecidas usando el "perezoso" manera, y también para comentar en cualquier lugar donde se use. Ahora no habrá un compilador ni nada que haga cumplir esas reglas, por lo que YMMV sigue siendo así.

Al final, para mí, las decisiones como esta se reducen a quién lo va a ver y la calidad de esos programadores. Si puede confiar en que sus colegas desarrolladores lo usarán correctamente y comentarán bien, hágalo, pero si no, es mejor hacerlo de una manera fácil de entender y seguir. / my 2cents

No creo que preocuparse de que un desarrollador no entienda sea un buen argumento para no hacer algo como esto ...

Si crees eso, entonces no podrías hacer nada por miedo a que alguien no entienda lo que hiciste

Puedes escribir un tutorial o algo en un repositorio central, aquí tenemos un wiki para este tipo de notas

En general, creo que es una buena idea de implementación (no querer iniciar un debate sobre si la carga diferida es una buena idea o no)

Lo que hago en este caso es crear un Fragmento de código de Visual Studio . Creo que eso es lo que realmente deberías hacer.

Por ejemplo, cuando creo controles ASP.NET, muchas veces tengo datos que se almacenan mucho en ViewState, así que creé un fragmento de código como este:

public Type Value
{
    get
    {
        if(ViewState["key"] == null)
            ViewState["key"] = someDefaultValue;
        return (Type)ViewState["key"];
    }
    set{ ViewState["key"] = value; }
}

De esta forma, el código se puede crear fácilmente con solo un poco de trabajo (definiendo el tipo, la clave, el nombre y el valor predeterminado). Es reutilizable, pero no tiene la desventaja de un código complejo que otros desarrolladores podrían no entender.

Me gusta su solución, ya que es muy inteligente, pero no creo que gane mucho al usarla. La carga diferida de un campo privado en una propiedad pública es definitivamente un lugar donde se puede duplicar el código. Sin embargo, esto siempre me ha parecido un patrón para usar en lugar de un código que debe ser refactorizado en un lugar común.

Su enfoque puede convertirse en una preocupación en el futuro si realiza alguna serialización. También es más confuso inicialmente entender lo que está haciendo con el tipo personalizado.

En general, aplaudo su intento y aprecio su inteligencia, pero sugeriría que recurra a su solución original por los motivos indicados anteriormente.

Personalmente, no creo que la clase LazyProperty ofrezca un valor suficiente para justificar su uso, especialmente teniendo en cuenta los inconvenientes que tiene para los tipos de valor (como mencionó Kent). Si necesita otra funcionalidad (como hacerla multiproceso), podría justificarse como una clase ThreadSafeLazyProperty.

En cuanto a la propiedad implícita, me gusta el "Valor" propiedad mejor. Es un poco más de tipeo, pero mucho más claro para mí.

Creo que esta es una idea interesante. Primero, recomendaría que oculte la propiedad Lazy del código de llamada. No desea filtrar en su modelo de dominio que es perezoso. Lo que estás haciendo con el operador implícito, así que guárdalo.

Me gusta cómo puede usar este enfoque para manejar y abstraer los detalles del bloqueo, por ejemplo. Si haces eso, creo que hay valor y mérito. Si agrega bloqueo, tenga cuidado con el patrón de doble bloqueo, es muy fácil equivocarse.

Puede usar iteradores de C #. La siguiente publicación explica un uso de muestra y las ventajas de usarlo.

http://hemanshubhojak.com/Home/Post?postId=3

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top