La declaración VB.NET 'With': ¿aceptar o evitar?
-
08-07-2019 - |
Pregunta
En el trabajo, con frecuencia estoy trabajando en proyectos donde se deben establecer numerosas propiedades de ciertos objetos durante su construcción o temprano durante su vida útil. En aras de la conveniencia y la legibilidad, a menudo uso la instrucción With
para establecer estas propiedades. Me parece que
With Me.Elements
.PropertyA = True
.PropertyB = "Inactive"
' And so on for several more lines
End With
Se ve mucho mejor que
Me.Elements.PropertyA = True
Me.Elements.PropertyB = "Inactive"
' And so on for several more lines
para declaraciones muy largas que simplemente establecen propiedades.
He notado que hay algunos problemas con el uso de With
durante la depuración; sin embargo, me preguntaba si había alguna razón convincente para evitar usar With
en la práctica . Siempre he asumido que el código generado a través del compilador para los dos casos anteriores es básicamente el mismo, por eso siempre he elegido escribir lo que siento que es más legible.
Solución
Si tiene nombres variables largos y terminaría con:
UserHandler.GetUser.First.User.FirstName="Stefan"
UserHandler.GetUser.First.User.LastName="Karlsson"
UserHandler.GetUser.First.User.Age="39"
UserHandler.GetUser.First.User.Sex="Male"
UserHandler.GetUser.First.User.Occupation="Programmer"
UserHandler.GetUser.First.User.UserID="0"
....and so on
entonces usaría WITH para hacerlo más legible:
With UserHandler.GetUser.First.User
.FirstName="Stefan"
.LastName="Karlsson"
.Age="39"
.Sex="Male"
.Occupation="Programmer"
.UserID="0"
end with
En el ejemplo posterior, incluso hay beneficios de rendimiento sobre el primer ejemplo porque en el primer ejemplo estoy buscando al usuario cada vez que accedo a una propiedad de usuario y en el caso WITH solo obtengo al usuario una vez.
Puedo obtener el aumento de rendimiento sin usar con, así:
dim myuser as user =UserHandler.GetUser.First.User
myuser.FirstName="Stefan"
myuser.LastName="Karlsson"
myuser.Age="39"
myuser.Sex="Male"
myuser.Occupation="Programmer"
myuser.UserID="0"
Pero preferiría la instrucción WITH, parece más limpia.
Y acabo de tomar esto como un ejemplo, así que no se queje de una clase con muchas palabras clave, otro ejemplo podría ser: WITH RefundDialog.RefundDatagridView.SelectedRows (0)
Otros consejos
En la práctica, no hay puntos realmente convincentes en su contra. No soy un fanático, pero es una preferencia personal, no hay datos empíricos que sugieran que la construcción With
es mala.
En .NET, compila exactamente el mismo código que la calificación completa del nombre del objeto, por lo que no hay penalización de rendimiento para este azúcar. Verifiqué esto compilando y luego desarmando la siguiente clase VB .NET 2.0:
Imports System.Text
Public Class Class1
Public Sub Foo()
Dim sb As New StringBuilder
With sb
.Append("foo")
.Append("bar")
.Append("zap")
End With
Dim sb2 As New StringBuilder
sb2.Append("foo")
sb2.Append("bar")
sb2.Append("zap")
End Sub
End Class
El desensamblaje es el siguiente: tenga en cuenta que las llamadas al método sb2
Append
se ven idénticas a las instrucciones de la instrucción With
para < code> sb :
.method public instance void Foo() cil managed
{
// Code size 91 (0x5b)
.maxstack 2
.locals init ([0] class [mscorlib]System.Text.StringBuilder sb,
[1] class [mscorlib]System.Text.StringBuilder sb2,
[2] class [mscorlib]System.Text.StringBuilder VB$t_ref$L0)
IL_0000: nop
IL_0001: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: stloc.2
IL_0009: ldloc.2
IL_000a: ldstr "foo"
IL_000f: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
IL_0014: pop
IL_0015: ldloc.2
IL_0016: ldstr "bar"
IL_001b: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
IL_0020: pop
IL_0021: ldloc.2
IL_0022: ldstr "zap"
IL_0027: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
IL_002c: pop
IL_002d: ldnull
IL_002e: stloc.2
IL_002f: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
IL_0034: stloc.1
IL_0035: ldloc.1
IL_0036: ldstr "foo"
IL_003b: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
IL_0040: pop
IL_0041: ldloc.1
IL_0042: ldstr "bar"
IL_0047: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
IL_004c: pop
IL_004d: ldloc.1
IL_004e: ldstr "zap"
IL_0053: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
IL_0058: pop
IL_0059: nop
IL_005a: ret
} // end of method Class1::Foo
Entonces, si te gusta y lo encuentras más legible, hazlo; No hay una razón convincente para no hacerlo.
(Por cierto, Tom , estoy interesado en saber qué sucedió con el depurador: puedo ' No recuerdo haber visto ningún comportamiento inusual en el depurador basado en una instrucción With
, así que tengo curiosidad por saber qué comportamiento viste).
Hay una diferencia entre usar With y hacer referencias repetidas a un objeto, lo cual es sutil pero debería tenerse en cuenta, creo.
Cuando se usa una instrucción WITH, crea una nueva variable local que hace referencia al objeto. Las referencias posteriores que usan .xx son referencias a propiedades de esa referencia local. Si durante la ejecución de la instrucción WITH, se cambia la referencia de variable original, el objeto al que hace referencia WITH no cambia. Considere:
Dim AA As AAClass = GetNextAAObject()
With AA
AA = GetNextAAObject()
'// Setting property of original AA instance, not later instance
.SomeProperty = SomeValue
End With
Entonces, la declaración WITH no es simplemente azúcar sintáctica, es realmente una construcción diferente. Si bien es poco probable que codifique algo explícito como el anterior, en algunas situaciones esto puede ocurrir inadvertidamente, por lo que debe tener en cuenta el problema. La situación más probable es cuando puede atravesar una estructura como una red de objetos cuyas interconexiones pueden cambiarse implícitamente mediante el establecimiento de propiedades.
Se trata de legibilidad. Como todo azúcar sintáctico, puede ser usado en exceso .
Abrácelo SI está configurando varios miembros de un objeto en unas pocas líneas
With myObject
.Property1 = arg1
.Property2 = arg2
...
Evite hacer cualquier otra cosa con " Con "
Si escribe un bloque With que abarca entre 50 y 100 líneas e involucra muchas otras variables, puede hacer que REALMENTE sea difícil recordar lo que se declaró en la parte superior del bloque. Por razones obvias, no proporcionaré un ejemplo de un código tan desordenado
Donde haga que el código sea más legible, hágalo. Cuando lo haga menos legible, evítelo, en particular, le sugiero que evite anidar con declaraciones.
C # 3.0 tiene esta característica únicamente para la inicialización de objetos:
var x = new Whatever { PropertyA=true, PropertyB="Inactive" };
Esto no solo es bastante necesario para LINQ, sino que también tiene sentido en términos de dónde la sintaxis no indica un olor a código. Por lo general, encuentro que cuando realizo muchas operaciones diferentes en un objeto más allá de su construcción inicial, esas operaciones deben encapsularse como una sola en el objeto mismo.
Una nota sobre su ejemplo: ¿realmente necesita el " Yo " ¿en absoluto? ¿Por qué no simplemente escribir:
PropertyA = True
PropertyB = "Inactive"
? Seguramente '' Yo '' está implícito en ese caso ...
Sospecharía del código que usa mucho esta palabra clave: si se usa para facilitar la configuración de muchas variables de instancia o propiedades, creo que esto puede indicar que sus clases son demasiado grandes ( Olor de clase grande ). Si lo usa para reemplazar largas cadenas de llamadas como esta:
UserHandler.GetUser.First.User.FirstName="Stefan"
UserHandler.GetUser.First.User.LastName="Karlsson"
UserHandler.GetUser.First.User.Age="39"
UserHandler.GetUser.First.User.Sex="Male"
UserHandler.GetUser.First.User.Occupation="Programmer"
UserHandler.GetUser.First.User.UserID="0"
entonces probablemente esté violando la Ley de Demeter
No uso VB.NET (solía usar VB simple) pero ...
¿Es obligatorio el punto inicial? Si es así, entonces no veo un problema. En Javascript, el resultado de usar con
es que una propiedad de un objeto se ve igual que una variable simple, y eso es muy peligroso, como no ve si está accediendo a una propiedad o una variable, y por lo tanto, con
es algo que debe evitarse.
No solo su uso es más fácil para los ojos, sino que para el acceso repetido a las propiedades de un objeto, es probable que sea más rápido, ya que el objeto se obtiene a través de la cadena del método solo una vez, y no una vez por cada propiedad. p>
Estoy de acuerdo con otras respuestas de que debe evitar el uso anidado de con
, por la misma razón que para evitar with
por completo en Javascript: porque ya no ver a qué objeto pertenece su propiedad.
El 'con' es básicamente la 'cascada' de Smalltalk. Es un patrón en el libro de patrones de mejores prácticas Smalltalk de Kent Beck.
Un resumen del patrón: utilícelo cuando tenga sentido agrupar los mensajes enviados al objeto. No lo use si solo se trata de algunos mensajes enviados al mismo objeto.
EVITE el WITH Block a toda costa (incluso legibilidad). Dos razones:
- la La documentación de Microsoft sobre Con ... Finalizar con dice que en algunas circunstancias, crea una copia de los datos en la pila, por lo que cualquier cambio que realice será descartado.
- Si lo usa para consultas LINQ, los resultados lambda NO encadenan y, por lo tanto, el resultado de cada cláusula intermedia se descarta.
Para describir esto, tenemos un ejemplo (roto) de un libro de texto sobre el cual mi compañero de trabajo tuvo que preguntarle al autor (de hecho es incorrecto, los nombres se han cambiado para proteger ... lo que sea):
Con dbcontext.Blahs
.OrderBy (Function (currentBlah) currentBlah.LastName)
.ThenBy (Function (currentBlah) currentBlah.FirstName)
.Load ()
Terminar con
OrderBy y ThenBy no tienen ningún efecto . SI reformatea el código SOLO soltando el y con Fin y agregando caracteres de continuación de línea al final de las primeras tres líneas ... funciona (como se muestra 15 páginas más tarde en el mismo libro de texto) .
No necesitamos más razones para buscar y destruir WITH Blocks. Solo tenían significado en un marco Interpretado .
Hay un problema al usarlo con estructuras, es decir, no puede establecer sus campos, ya que está trabajando en una copia local (hecha en el momento de la entrada con el bloque) del " con " expresión y no funciona con una (copia de) referencia de objeto en ese caso:
El tipo de datos de objectExpression puede ser cualquier clase o tipo de estructura o incluso un tipo elemental de Visual Basic como Integer. Si objectExpression da como resultado cualquier cosa que no sea un objeto, puede solo lea los valores de sus miembros o métodos de invocación, y obtendrá un error si intenta asignar valores a miembros de una estructura utilizada en un Con ... Finalizar con la declaración. Este es el mismo error que obtendrías si invocó un método que devolvió una estructura y accedió de inmediato y asignó un valor a un miembro del resultado de la función, como GetAPoint (). X = 1. El problema en ambos casos es que la estructura existe solo en la pila de llamadas, y no hay forma de que se modifique miembro de la estructura en estas situaciones puede escribir en una ubicación tal que cualquier otro código en el programa puede observar el cambio.
La objectExpression se evalúa una vez, al ingresar al bloque. usted no se puede reasignar la objectExpression desde dentro del bloque With.
supongo que el compilador podría haber sido un poco más inteligente si pasa a la instrucción un nombre de estructura en lugar de una expresión que devuelve una estructura, pero parece que no lo es