Pregunta

Siendo principalmente un desarrollador de C ++, la ausencia de RAII (La adquisición de recursos es la inicialización) en Java y .NET siempre me ha molestado. El hecho de que la responsabilidad de la limpieza se transfiere del escritor de la clase a su consumidor (mediante intente finalmente o .NET's using construct ) parece ser marcadamente inferior.

Veo por qué en Java no hay soporte para RAII ya que todos los objetos se encuentran en el montón y el recolector de basura no admite la destrucción determinista, pero en .NET con la introducción de tipos de valor (estructura ) tenemos el candidato (aparentemente) perfecto para RAII. Un tipo de valor que se crea en la pila tiene un alcance bien definido y se puede usar la semántica del destructor de C ++. Sin embargo, el CLR no permite que un tipo de valor tenga un destructor.

Mis búsquedas aleatorias encontraron un argumento de que si un tipo de valor es encuadrado cae bajo la jurisdicción del recolector de basura y, por lo tanto, su destrucción se vuelve no determinista. Siento que este argumento no es lo suficientemente fuerte, los beneficios de RAII son lo suficientemente grandes como para decir que un tipo de valor con un destructor no puede ser encuadrado (o usado como un miembro de la clase).

Para resumir una larga historia, mi pregunta es : ¿existen otras razones por las que no se puedan utilizar los tipos de valores para introducir RAII en .NET? (¿O crees que mi argumento sobre las ventajas obvias de RAII es defectuoso?)

Editar: no debí haber formulado la pregunta con claridad, ya que las primeras cuatro respuestas no han acertado. Conozco a sobre Finalize y sus características no deterministas, conozco el uso y creo que estas dos opciones son inferiores a RAII. usar es una cosa más que el consumidor de una clase debe recordar (¿cuántas personas se olvidaron de poner un StreamReader en un usando ?). Mi pregunta es filosófica sobre el diseño del lenguaje, ¿por qué es así y puede mejorarse?

Por ejemplo, con un tipo de valor genérico determinista y destructivo, puedo hacer que las palabras clave utilizando y lock sean redundantes (alcanzables por las clases de la biblioteca):

    public struct Disposer<T> where T : IDisposable
    {
        T val;
        public Disposer(T t) { val = t; }
        public T Value { get { return val; } }
        ~Disposer()  // Currently illegal 
        {
            if (val != default(T))
                val.Dispose();
        }
    }

No puedo evitar terminar con una cita apropiada que una vez vi pero que actualmente no puedo encontrar su origen.

  

Puedes tomar mi destrucción determinista cuando mi mano fría y muerta se salga del alcance. - Anon

¿Fue útil?

Solución

Un título mejor sería " ¿Por qué no hay RAII en C # / VB " ;. C ++ / CLI (La evolución del aborto que se administró C ++) tiene RAII en el mismo sentido que C ++. Todo es simplemente sintaxis de azúcar para el mismo patrón de finalización que el resto de los lenguajes de CLI (los Destructores en objetos administrados para C ++ / CLI son finalizadores), pero está ahí.

Puede que te guste http://blogs.msdn.com /hsutter/archive/2004/07/31/203137.aspx

Otros consejos

Excelente pregunta y una que me ha molestado mucho. Parece que los beneficios de RAII se perciben de manera muy diferente. En mi experiencia con .NET, la falta de recopilación de recursos determinista (o al menos confiable) es uno de los principales inconvenientes. De hecho, .NET me ha obligado varias veces a emplear arquitecturas completas para tratar con recursos no administrados que podría (pero puede que no) requieran una recopilación explícita. Lo que, por supuesto, es un gran inconveniente porque hace que la arquitectura general sea más difícil y aleja la atención del cliente de los aspectos más centrales.

Brian Harry tiene una buena publicación sobre los fundamentos aquí .

Aquí hay un extracto:

  

¿Qué pasa con la finalización determinista y los tipos de valor (estructuras)?

     

-------------- He visto muchas preguntas sobre las estructuras que tienen   destructores, etc. Esto vale la pena.   comentario. Hay una variedad de   problemas por qué algunos idiomas no lo hacen   tenerlos.

     

(1) composición - No te dan   Vida determinista en el general.   caso para los mismos tipos de composición   razones descritas anteriormente. Ninguna   clase no determinista que contiene uno   no llamaría al destructor hasta que   fue finalizado por el CG de todos modos.

     

(2) constructores de copias: el único lugar   donde realmente estaría bien es en   apilar los locales asignados. Ellos serian   Con alcance al método y todo sería   genial. Desafortunadamente, con el fin de obtener   Esto para que realmente funcione, también tienes que   Añadir copia constructores y llamarlos   Cada vez que se copia una instancia.   Este es uno de los más feos y más   Cosas complejas sobre C ++. Terminas   conseguir código ejecutando todo el   Lugar donde no lo esperas. Eso   causa un montón de problemas de lenguaje.   Algunos diseñadores de idiomas han optado por   mantente alejado de esto.

     

Digamos que creamos estructuras con   destructores, pero añadió un montón de   Restricciones para hacer su comportamiento.   sensible ante los problemas   encima. Las restricciones serían   algo como:

     

(1) Solo puedes declararlos como locales   variables

     

(2) Solo puedes pasarlos   by-ref

     

(3) No puedes asignarlos, tu   Solo se puede acceder a los campos y llamar.   Métodos en ellos.

     

(4) No puedes boxear   ellos.

     

(5) Problemas al usarlos a través de   Reflexión (encuadernación tardía) porque eso   por lo general implica el boxeo.

     

tal vez más,   pero eso es un buen comienzo.

     

¿De qué servirían estas cosas? haría   en realidad creas un archivo o una   clase de conexión de base de datos que puede   SOLO ser utilizado como una variable local? yo   No creas que nadie realmente lo haría.   Lo que harías en cambio es crear un   conexión de propósito general y luego   crear un envoltorio autodestructido para   utilizar como una variable local con ámbito. los   la persona que llama entonces escogería lo que ellos   quería utilizar. Tenga en cuenta que la persona que llama hizo un   decisión y no es del todo   encapsulado en el propio objeto.   Dado que podrías usar algo   como las sugerencias que vienen en una   par de secciones.

El reemplazo de RAII en .NET es el patrón de uso, que funciona casi tan bien una vez que te acostumbras.

Lo más cercano a eso es el operador stackalloc muy limitado.

Hay algunos subprocesos similares si los busca pero, básicamente, a lo que se reduce es que si desea RAII en .NET, simplemente implemente un tipo IDisposable y use el " uso de " Declaración para obtener la disposición determinista. De esa manera, muchas de las mismas ideologías se pueden implementar y utilizar de una manera un poco más prolija.

En mi humilde opinión, las grandes cosas que VB.net y C # necesitan son:

  1. a " utilizando " Declaración de campos, lo que causaría que el compilador genere código para eliminar todos los campos etiquetados. El comportamiento predeterminado debe ser que el compilador haga que una implementación de clase sea IDisponible si no lo hace, o inserte la lógica de eliminación antes del inicio de la rutina de eliminación principal para cualquiera de una serie de patrones de implementación de IDisposal comunes, o bien use un atributo para especificar que Las cosas de eliminación deben ir en una rutina con un nombre particular.
  2. un medio de eliminación determinista de los objetos cuyos constructores y / o inicializadores de campo lanzan una excepción, ya sea por un comportamiento predeterminado (llamando al método de eliminación predeterminado) o un comportamiento personalizado (llamando a un método con un nombre particular) .
  3. Para vb.net, un método generado automáticamente para anular todos los campos WithEvent.

Todos estos pueden ser bien guardados en vb.net, y algo menos en C #, pero el soporte de primera clase para ellos mejoraría ambos idiomas.

Puedes hacer una forma de RAII en .net y java usando los métodos finalize (). Se llama a una sobrecarga finalize () antes de que la clase sea limpiada por el GC y, por lo tanto, se puede usar para limpiar cualquier recurso que la clase no debe conservar (mutexes, sockets, manejadores de archivos, etc.). Aunque todavía no es determinista.

Con .NET puede hacer algo de esto de manera determinista con la interfaz IDisposable y la palabra clave de uso, pero esto tiene limitaciones (el uso de construcción cuando se usa es necesario para el comportamiento determinista, aún no hay desasignación de memoria determinística, no se usa automáticamente en las clases, etc. ).

Y sí, creo que hay un lugar para que las ideas RAII se introduzcan en .NET y otros lenguajes administrados, aunque el mecanismo exacto podría debatirse sin cesar. La única otra alternativa que podría ver sería presentar un GC que podría manejar la limpieza de recursos arbitrarios (no solo la memoria), pero luego tiene problemas cuando dichos recursos deben ser liberados de manera determinista.

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