2011-01-05 14 views
27

Después de depurar un problema particularmente complicado en VB.NET que implica el orden en el que se inicializan las variables de instancia, descubrí que hay una discrepancia de ruptura entre el comportamiento que esperaba de C# y el comportamiento real en VB.NET.¿Se puede obligar a VB.NET a inicializar variables de instancia ANTES de invocar el constructor de tipo base?

Nota bene: Esta pregunta se refiere a una ligera discrepancia en los comportamientos de VB.NET y C#. Si usted es un fanático del lenguaje que no puede proporcionar una respuesta que no sea "es por eso que debe usar C#, noob", no hay nada para que usted vea aquí; amablemente muévete.

Específicamente, esperaba el comportamiento esbozado por el C# Language Specification (énfasis añadido):

Cuando un constructor de instancias no tiene inicializador constructor, o tiene un inicializador constructor de la forma base(...), que constructor realiza implícitamente las inicializaciones especificadas por los inicializadores de variables de los campos de instancia declarados en su clase. Esto corresponde a una secuencia de asignaciones que se ejecutan inmediatamente después de la entrada al constructor y antes de la invocación implícita del constructor de la clase base directa. Los inicializadores de variable se ejecutan en el orden textual en el que aparecen en la declaración de clase.

contraste que con la parte de la especificación del lenguaje VB.NET en relación Instance Constructors, que dice (el subrayado es nuestro):

Cuando la primera declaración de un constructor es de la forma MyBase.New(...), el constructor realiza implícitamente la inicializaciones especificadas por los inicializadores variables de las variables de instancia declaradas en el tipo. Esto corresponde a una secuencia de asignaciones que se ejecutan inmediatamente después de invocar el constructor de tipo base directo. Tal ordenamiento asegura que todas las variables de instancia base sean inicializadas por sus inicializadores variables antes de que se ejecuten las sentencias que tienen acceso a la instancia.

La discrepancia aquí es inmediatamente obvia. C# inicializa las variables de nivel de clase antes de llamando al constructor base. VB.NET hace exactamente lo contrario, al parecer prefiere llamar al constructor base antes de estableciendo los valores de los campos de instancia.

Si quiere ver algún código, this related question proporciona un ejemplo más concreto del comportamiento divergente. Desafortunadamente, no proporciona ninguna pista sobre cómo se puede obligar a VB.NET a seguir el modelo establecido por C#.

Estoy menos interesado en por qué los diseñadores de los dos idiomas eligieron enfoques tan divergentes que en posibles soluciones para el problema. En definitiva, mi pregunta es la siguiente: ¿Hay alguna forma de que pueda escribir o estructurar mi código en VB.NET para forzar las variables de instancia que se inicializarán antes de el constructor del tipo base se llama, como es el comportamiento estándar en C# ?

+1

No creo que sea posible cambiar esto, y el compilador de VB luchará contra cualquier intento de hacerlo. Si confía en tal comportamiento, generalmente es una indicación de problemas en otras partes de su código. Ha habido consejos contra llamar a los miembros virtuales dentro de los constructores durante mucho tiempo (como en el código de ejemplo en la otra pregunta) –

+0

@Damien: tiene razón acerca de que es una mala práctica llamar miembros virtuales dentro de los constructores. Lamentablemente, esa decisión no fue mía. Estoy creando una subclase de un tipo proporcionado por el Framework al mismo tiempo que anulo uno de sus métodos virtuales. Estoy bastante seguro de que los problemas se encuentran fuera de mi código, y lo único que se me ocurre para solucionar esto es gritar "feo truco" para mí. –

+0

vea también http://stackoverflow.com/q/12585555/185593 – serhio

Respuesta

8

Si tiene miembros virtuales que se invocarán durante la construcción (en contra de los mejores consejos, pero ya hemos acordado eso), entonces necesita mover su inicialización en un método separado, que puede protegerse contra múltiples llamadas (es decir, si init ya ha sucedido, regrese de inmediato). Ese método será invocado por los miembros virtuales y su constructor, antes de que se basen en que se haya producido la inicialización.

Es un poco desordenado, y puede representar una penalización de rendimiento menor, pero hay poco más que puede hacer en VB.

2

Cualquier clase bien escrita debe garantizar que cualquier miembro virtual que pueda ser invocado en una instancia construida parcialmente se comporte de forma sensata. C# adopta la filosofía de que una instancia de clase cuyos iniciadores de campo se hayan ejecutado estará en un estado suficientemente sensible para permitir el uso de métodos virtuales. VB.net adopta la filosofía de permitir que los inicializadores de campo utilicen un objeto parcialmente construido (y, con un poco de trabajo, cualquier parámetro que se pase al constructor) es más útil que una garantía de que los inicializadores de campo se ejecutarán antes que cualquier virtual los métodos se llaman.

En mi humilde opinión, el enfoque correcto desde el punto de vista del diseño del lenguaje habría sido proporcionar un medio conveniente para indicar que los inicializadores de campo especificados deberían ejecutarse "temprano" o "tarde", ya que hay veces que cada uno puede ser útil (aunque Prefiero el estilo "tardío", ya que permite que los parámetros del constructor estén disponibles para los inicializadores de campo). Por ejemplo:

 
Class ParamPasserBase(Of T) ' Generic class for passing one constructor parameter 
    Protected ConstructorParam1 As T 
    Sub New(Param As T) 
    ConstructorParam1 = Param 
    End SUb 
End Class 
Class MyThing 
    Inherits ParamPasserBase(Of Integer) 
    Dim MyArray(ConstructorParam1-1) As String 
    Sub New(ArraySize As Integer) 
    MyBase.New(ArraySize) 
    End Sub 
    ... 
End Class 

En C#, no hay manera agradable tener declaraciones o inicializadores de campo hacen uso de argumentos pasados ​​a un constructor. En vb, se puede hacer razonablemente limpio como se ilustra arriba. Tenga en cuenta que en vb, también es posible utilizar un parámetro de constructor de referencia por referencia para contrabandear una copia del objeto en construcción antes de ejecutar cualquier inicializador de campo; si la rutina Dispose de una clase está escrita correctamente para tratar objetos parcialmente construidos, un objeto que arroje su constructor puede limpiarse adecuadamente.

+1

No estoy seguro de seguir lo que está tratando de decir aquí. No tengo la capacidad de rediseñar ningún idioma, y ​​no estoy particularmente discutiendo sobre el mérito relativo de las opciones. Además, ¿de dónde viene 'RIAA()' en tu muestra de código? Usted dice que uno puede hacer esto ahora en VB.NET. No estoy familiarizado con la sintaxis. ¿'RIAA()' es una clase contenedora personalizada que has escrito? ¿Y cómo es eso diferente de una declaración 'using'? Más allá de eso, ¿cuál es la distinción entre VB.NET "anterior" y VB.NET "más nuevo"? Uno es tipo seguro y el otro no? ¿Qué tiene que ver la destrucción con la pregunta? –

+0

@Cody Gray: RIAA es un método de clase que registra un IDisposable para la limpieza automática determinista (en el estilo de C++/RIAA) y luego lo devuelve. En vb.net, los inicializadores de campo se ejecutan después de que se crea la lista de elementos desechables, por lo que el método RIAA puede agregarlos a esa lista. VB.Net también permite que un objeto en construcción sea sacado de contrabando del constructor, por lo que el constructor puede ser envuelto en un método de fábrica que limpiará cualquier objeto IDisposable registrado si el constructor lanza. – supercat

+0

¿Puede darme un enlace a donde se discute este método de RIAA? No puedo encontrar la documentación en línea.Tampoco estoy seguro de qué elementos de eliminación tiene que ver con la inicialización de variables de instancia antes de que se llame al constructor de la clase base. Mi pregunta no es ni siquiera sobre objetos, y mucho menos sobre aquellos que implementan 'IDisposable'. El caso de prueba era en realidad un tipo 'Integer' o' Boolean', ambos son tipos de valores y no es necesario eliminarlos. RIAA es tan inútil aquí como irrelevante. –

Cuestiones relacionadas