name keywords google etiquetas ejemplos description c# properties keyword

c# - google - meta name keywords



Problemas al agregar una palabra clave `lazy` a C# (7)

Tengo curiosidad por el tipo de consideraciones que una característica como esta debería tener que pasar.

Primero, escribo un blog sobre este tema, entre otros. Ver mi viejo blog:

http://blogs.msdn.com/b/ericlippert/

y mi nuevo blog:

http://ericlippert.com

para muchos artículos sobre diversos aspectos del diseño del lenguaje.

En segundo lugar, el proceso de diseño de C # ahora está abierto para su visualización al público, por lo que puede ver por sí mismo lo que el equipo de diseño de idiomas considera al examinar nuevas sugerencias de funciones. Ver https://github.com/dotnet/roslyn/ para más detalles.

¿Qué costos implicaría agregar este tipo de palabra clave a la biblioteca?

Depende de muchas cosas. Por supuesto, no hay características baratas, fáciles. Solo hay características menos costosas y menos difíciles. En general, los costos son los relacionados con el diseño, la especificación, la implementación, las pruebas, la documentación y el mantenimiento de la función. También hay costos más exóticos, como el costo de oportunidad de no hacer una función mejor, o el costo de elegir una función que interactúa mal con las características futuras que podríamos querer agregar.

En este caso, la característica sería simplemente hacer que la palabra clave "floja" sea un azúcar sintáctico para usar Lazy<T> . Esa es una característica bastante sencilla, que no requiere un gran análisis sintáctico o semántico.

¿En qué situaciones sería problemático?

Puedo pensar en una serie de factores que me harían retroceder en la función.

En primer lugar, no es necesario; es simplemente un azúcar conveniente. Realmente no agrega nueva potencia al lenguaje. Los beneficios no parecen valer los costos.

En segundo lugar, y más importante, consagra un tipo particular de pereza en el lenguaje. Hay más de un tipo de pereza, y podemos elegir mal.

¿Cómo hay más de un tipo de pereza? Bueno, piensa en cómo se implementaría. Las propiedades ya son "flojas" porque sus valores no se calculan hasta que se llama a la propiedad, pero desea más; desea una propiedad que se llame una vez, y luego el valor se almacenará en caché para la próxima vez. Por "perezoso" esencialmente te refieres a una propiedad memorable. ¿Qué garantías necesitamos poner en marcha? Hay muchas posibilidades:

Posibilidad n. ° 1: no es seguro para nada. Si llama a la propiedad por primera vez en dos subprocesos diferentes, puede pasar cualquier cosa. Si desea evitar las condiciones de carrera, debe agregar la sincronización usted mismo.

Posibilidad n. ° 2: Threadsafe, de modo que dos llamadas a la propiedad en dos hilos diferentes llaman a la función de inicialización y luego compiten para ver quién completa el valor real en la memoria caché. Es de suponer que la función devolverá el mismo valor en ambos subprocesos, por lo que el costo adicional aquí es meramente en la llamada extra desperdiciada. Pero la memoria caché es segura para los hilos y no bloquea ningún hilo. (Porque la memoria caché se puede escribir con código de bloqueo bajo o sin bloqueo).

El código para implementar la seguridad de subprocesos tiene un costo, incluso si es un código de bloqueo bajo. ¿Es ese costo aceptable? La mayoría de las personas escribe lo que efectivamente son programas de un solo hilo; ¿Parece correcto agregar la sobrecarga de la seguridad del hilo a cada llamada de propiedad perezosa, ya sea necesaria o no?

Posibilidad n. ° 3: protección contra subprocesos tal que existe una gran garantía de que la función de inicialización solo se invocará una vez; no hay carrera en el caché. El usuario puede tener una expectativa implícita de que la función de inicialización solo se llame una vez; podría ser muy costoso y dos llamadas en dos hilos diferentes podrían ser inaceptables. Implementar este tipo de pereza requiere una sincronización completa donde es posible que un hilo se bloquee indefinidamente mientras el método perezoso se ejecuta en otro hilo. También significa que podría haber interbloqueos si hay un problema de orden de bloqueo con el método perezoso.

Eso agrega un costo aún mayor a la función, un costo que es asumido igualmente por las personas que no lo aprovechan (porque están escribiendo programas de subproceso único).

Entonces, ¿cómo lidiamos con esto? Podríamos agregar tres características: "flojo, no peligroso", "flojo con las razas" y "flojo, seguro con el bloqueo y quizás bloqueos". Y ahora la función se volvió mucho más costosa y mucho más difícil de documentar. Esto produce un enorme problema de educación del usuario. Cada vez que le das a un desarrollador una opción como esta, les presentas la oportunidad de escribir errores terribles.

En tercer lugar, la característica parece débil como se dijo. ¿Por qué debería la pereza ser aplicada meramente a las propiedades? Parece que esto podría aplicarse generalmente a través del sistema de tipos:

lazy int x = M(); // doesn''t call M() lazy int y = x + x; // doesn''t add x + x int z = y * y; // now M() is called once and cached. // x + x is computed and cached // y * y is computed

Tratamos de no hacer las funciones pequeñas y débiles si hay una característica más general que es una extensión natural de la misma. Pero ahora estamos hablando de costos de diseño e implementación realmente serios.

¿Lo encontrarías útil?

¿Personalmente? No es realmente útil. Escribo un montón de código perezoso sencillo de bloqueo bajo sobre todo usando Interlocked.Exchange. (No me importa si el método perezoso se ejecuta dos veces y uno de los resultados se descarta, mis métodos perezosos nunca son tan caros). El patrón es sencillo, sé que es seguro, nunca hay objetos adicionales asignados para el delegado o las cerraduras, y si tengo algo un poco más complejo, siempre puedo usar Lazy<T> para hacer el trabajo por mí. Sería una pequeña conveniencia.

Me encantaría escribir un código como este:

class Zebra { public lazy int StripeCount { get { return ExpensiveCountingMethodThatReallyOnlyNeedsToBeRunOnce(); } } }

EDITAR: ¿Por qué? Creo que se ve mejor que:

class Zebra { private Lazy<int> _StripeCount; public Zebra() { this._StripeCount = new Lazy(() => ExpensiveCountingMethodThatReallyOnlyNeedsToBeRunOnce()); } public lazy int StripeCount { get { return this._StripeCount.Value; } } }

La primera vez que llame a la propiedad, ejecutará el código en el bloque get , y luego simplemente devolverá el valor.

Mis preguntas:

  1. ¿Qué costos implicaría agregar este tipo de palabra clave a la biblioteca?
  2. ¿En qué situaciones sería problemático?
  3. ¿Lo encontrarías útil?

No estoy empezando una cruzada para incluir esto en la próxima versión de la biblioteca, pero tengo curiosidad por el tipo de consideraciones que una característica como esta debería tener que pasar.



¿Has probado / Dou te refieres a esto?

private Lazy<int> MyExpensiveCountingValue = new Lazy<int>(new Func<int>(()=> ExpensiveCountingMethodThatReallyOnlyNeedsToBeRunOnce())); public int StripeCount { get { return MyExpensiveCountingValue.Value; } }

EDITAR:

después de la edición de tu publicación, agregaría que tu idea es definitivamente más elegante, ¡pero sigue teniendo la misma funcionalidad!


Eche un vistazo al tipo Lazy<T> . Además, pregúntele a Eric Lippert acerca de agregar cosas como esta al idioma, sin duda tendría una opinión.


Es poco probable que se agregue al lenguaje C # porque puede hacerlo usted mismo fácilmente, incluso sin Lazy<T> .

Un ejemplo simple, pero no seguro para subprocesos :

class Zebra { private int? stripeCount; public int StripeCount { get { if (this.stripeCount == null) { this.stripeCount = ExpensiveCountingMethodThatReallyOnlyNeedsToBeRunOnce(); } return this.stripeCount; } } }


La biblioteca del sistema ya tiene una clase que hace lo que desea: Lazy<T>

Estoy seguro de que podría integrarse en el lenguaje, pero como Eric Lippert le dirá que agregar características a un idioma no es algo que se tome a la ligera. Se deben considerar muchas cosas, y la relación beneficio / costo debe ser muy buena. Como Lazy<T> ya maneja esto bastante bien, dudo que veremos esto pronto.


Si no te importa usar un compilador CciSharp , CciSharp tiene esta característica :

class Zebra { [Lazy] public int StripeCount { get { return ExpensiveCountingMethodThatReallyOnlyNeedsToBeRunOnce(); } } }