La statement ‘con’ de VB.NET: ¿aceptar o evitar?

En el trabajo, con frecuencia trabajo en proyectos en los que deben establecerse numerosas propiedades de ciertos objetos durante su construcción o temprano durante su vida. Por razones de conveniencia y legibilidad, a menudo uso la statement With para establecer estas propiedades. encontré eso

 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.

Me he dado cuenta de 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 supuesto que el código generado a través del comstackdor para los dos casos anteriores es básicamente el mismo y por eso siempre he elegido escribir lo que considero más legible.

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 último ejemplo, incluso hay un beneficio de rendimiento con respecto al primer ejemplo, porque en el primer ejemplo estoy obteniendo el usuario cada vez que accedo a una propiedad del usuario y, en el caso de WITH, solo alcanzo al usuario una vez.

Puedo obtener la ganancia de rendimiento sin usar, 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 statement WITH en su lugar, parece más limpia.

Y tomé esto como un ejemplo, así que no te quejes por una clase con muchas palabras clave, otro ejemplo podría ser: WITH RefundDialog.RefundDatagridView.SelectedRows (0)

En la práctica, no hay puntos convincentes contra eso. No soy fanático, pero esa es una preferencia personal, no hay datos empíricos que sugieran que el constructo With sea ​​malo.

En .NET, comstack exactamente el mismo código que la calificación completa del nombre del objeto, por lo que no hay una penalización de rendimiento para este azúcar. Lo comprobé comstackndo 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 desassembly es el siguiente: tenga en cuenta que las llamadas al método Append sb2 son idénticas a las llamadas a la statement With para 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, ve por él; no hay una razón convincente para no hacerlo.

(Por cierto, Tom , estoy interesado en saber qué pasó con el depurador; no recuerdo haber visto ningún comportamiento inusual en el depurador basado en una statement With , así que tengo curiosidad por saber qué comportamiento usaste ver.)

Hay una diferencia entre usar With y hacer referencias repetitivas a un objeto, lo cual es sutil, pero debería tenerse en cuenta, creo.

Cuando se usa una statement WITH, crea una nueva variable local que hace referencia al objeto. Las referencias posteriores que usan .xx son referencias a las propiedades de esa referencia local. Si durante la ejecución de la statement WITH, se cambia la referencia de la variable original, el objeto al que hace referencia el WITH no cambia. Considerar:

 Dim AA As AAClass = GetNextAAObject() With AA AA = GetNextAAObject() '// Setting property of original AA instance, not later instance .SomeProperty = SomeValue End With 

Entonces, la statement 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 podría ocurrir inadvertidamente, por lo que debe tener en cuenta el problema. La situación más probable es donde puede estar atravesando una estructura, como una red de objetos cuyas interconexiones pueden modificarse implícitamente configurando propiedades.

Se trata de la legibilidad. Como todo el azúcar sintáctico, puede ser usado en exceso .

Acéptalo si estás 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 50-100 líneas e involucra muchas otras variables, puede hacer REALMENTE difícil recordar lo que se declaró en la parte superior del bloque. Por razones obvias, no proporcionaré un ejemplo de código tan desordenado

Cuando haga que el código sea realmente más legible, adelante. 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 syntax no indica un olor a código. Normalmente 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 tu ejemplo: ¿realmente necesitas el “Yo”? ¿Por qué no solo escribir?

 PropertyA = True PropertyB = "Inactive" 

? Seguramente “Me” está implicado en ese caso …

Sería sospechoso de código que usa mucho esta palabra clave: si se usa para facilitar el establecimiento 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 cadenas largas 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és violando la Ley Demeter

No uso VB.NET (solía usar VB simple) pero …

¿El punto inicial es obligatorio? Si es así, entonces no veo un problema. En Javascript, el resultado de usar with es que una propiedad de un objeto se ve exactamente igual que una variable simple, y eso es muy peligroso, ya que no se ve si está accediendo a una propiedad o una variable, y por lo tanto, with es algo para evitar.

No solo es más fácil su uso para los ojos, sino también para el acceso repetido a las propiedades de un objeto, es probable que sea más rápido, ya que el objeto se busca a través de la cadena de métodos solo una vez, y no una vez por cada propiedad.

Estoy de acuerdo con otras respuestas con las que deberías evitar el uso nested de, por la misma razón que por qué evitarlas with completo en JavaScript: porque ya no ves a qué objeto pertenece tu propiedad.

El ‘con’ es básicamente la ‘cascada’ de Smalltalk. Es un patrón en el libro Smalltalk Best Patterns Patterns de Kent Beck.

Un resumen del patrón: úselo cuando tenga sentido agrupar los mensajes enviados al objeto. No lo use si resultan ser algunos mensajes enviados al mismo objeto.

EVITE el locking WITH a toda costa (incluso legibilidad). Dos razones:

  1. la Documentación de Microsoft sobre Con … Fin Con dice que en algunas circunstancias, crea una copia de los datos en la stack, por lo que cualquier cambio que realice será descartado.
  2. 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 que mi compañero de trabajo tuvo que preguntarle al autor (de hecho es incorrecto, los nombres han sido cambiados para proteger … lo que sea):

Con dbcontext.Blahs
.OrderBy (Function (currentBlah) currentBlah.LastName)
.ThenBy (Function (currentBlah) currentBlah.FirstName)
.Carga()
Terminar con

El OrderBy y ThenBy no tienen ningún efecto . SI vuelve a formatear el código SÓLO bajando el Con y el Fin con, y agregando caracteres de continuación de línea al final de las tres primeras líneas … funciona (como se muestra 15 páginas más adelante 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 error al usarlo con estructuras, también conocido como no se pueden establecer sus campos, ya que está trabajando en una copia local (hecha en el momento de la entrada en el bloque) de la expresión “con” y no está trabajando con una (copia de una) referencia de objeto en ese caso:

El tipo de datos de objectExpression puede ser de cualquier clase o tipo de estructura o incluso de un tipo elemental de Visual Basic como Integer. Si objectExpression resulta en algo que no sea un objeto, solo puede leer los valores de sus miembros o invocar métodos, y obtendrá un error si intenta asignar valores a los miembros de una estructura utilizada en una statement With … End With. Este es el mismo error que obtendría si invocara un método que devolviera una estructura y accediera inmediatamente y le asignara un valor a un miembro del resultado de la función, como GetAPoint (). X = 1. El problema en ambos casos es que el la estructura existe solo en la stack de llamadas, y no hay forma de que un miembro de la estructura modificada en estas situaciones pueda escribir en una ubicación de manera que cualquier otro código en el progtwig pueda observar el cambio.

ObjectExpression se evalúa una vez, al ingresar al bloque. No puede reasignar objectExpression desde el bloque With.

https://docs.microsoft.com/en-us/dotnet/visual-basic/language-reference/statements/with-end-with-statement

Supongo que el comstackdor 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 es

Estoy bastante seguro de que esto es solo VB.net, pero podría estar equivocado. Siendo un chico C #, siempre estuve un poco celoso de ese azúcar sintáctico.