Saturday, February 19, 2011

The VB.NET 'With' Statement - embrace or avoid?

At work, I'm frequently working on projects where numerous properties of certain objects have to be set during their construction or early during their lifetime. For the sake of convenience and readability, I often use the With statement to set these properties. I find that

With Me.Elements
    .PropertyA = True
    .PropertyB = "Inactive"
    ' And so on for several more lines
End With

Looks much better than

Me.Elements.PropertyA = True
Me.Elements.PropertyB = "Inactive"
' And so on for several more lines

for very long statements that simply set properties.

I've noticed that there are some issues with using With while debugging; however, I was wondering if there were any compelling reasons to avoid using With in practice? I've always assumed the code generated via the compiler for the above two cases is basically the same which is why I've always chosen to write what I feel to be more readable.

From stackoverflow
  • I'm pretty sure this is VB.net only, but I could be wrong. Being a C# guy I was always a little jealous of that syntactic sugar.

    Jon Skeet : C# 3.0 has it for object initialization, where it's frequently useful. If you're doing this at other times in the object's life cycle, that sounds like a code smell to me, suggesting that there should probably be one combined operation which does all the appropriate things.
    kenny : Where I've seen it useful is in external objects with lots of properties, but @Jon's probably right, obj-init works there usually or throw it into a wrapper method.
    Stefan : There are other .Net languages that has the WITH statement.
    JosephStyons : Delphi also has the WITH statement.
  • Where it makes the code genuinely more readable, go for it. Where it makes it less readable, avoid it - in particular, I suggest you avoid nesting With statements.

    C# 3.0 has this feature solely for object initialization:

    var x = new Whatever { PropertyA=true, PropertyB="Inactive" };
    

    This is not only pretty much required for LINQ, but it also makes sense in terms of where the syntax doesn't indicate a code smell. I usually find that when I'm performing many different operations on an object beyond its initial construction, those operations should be encapsulated as a single one on the object itself.

    One note about your example - do you really need the "Me" at all? Why not just write:

    PropertyA = True
    PropertyB = "Inactive"
    

    ? Surely "Me" is implied in that case...

    Tom : I was just doing that for a concise example, not to really demonstrate how I'm using in real practice.
    bart : Isn't it just typical how hard it is to pull realistic examples out of thin air... they always seem simpler than a real world case and always somebody somebody comes up with a way to simplify just the example, but not the general case.
  • If you have long variablenames and would end up with:

    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
    

    then I would use WITH to make it more readable:

    With UserHandler.GetUser.First.User
        .FirstName="Stefan"
        .LastName="Karlsson"
        .Age="39"
        .Sex="Male"
        .Occupation="Programmer"
        .UserID="0"
    end with
    

    In the later example there are even performance benefit over the first example because in the first example Im fetching the user every time I access a user property and in the WITH-case I only fetch the user one time.

    I can get the performance gain without using with, like this:

    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"
    

    But I would go for the WITH statement instead, it looks cleaner.

    And I just took this as an example so dont complain over a class with many keywords, another example could be like: WITH RefundDialog.RefundDatagridView.SelectedRows(0)

  • I would be suspicious of code that uses a lot this keyword: if it is used to make easier to set lots of instance variables or properties I think this may indicate that your classes are too large ( Large Class smell ). If you use it to replace long chains of calls like this:

    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"
    

    then you are probably violating Demeter Law

    Stefan : It was only to show an example. It could be long variablenames in combination with one or two keywords..
    Stefan : Like this for example: WITH RefundDialog.RefundDatagridView.SelectedRows(0)
    Jeremy Wiebe : I'd argue that exposing the entire GridView contained in the RefundDialog to external code is also a violation of the Law of Demeter. In my experience, almost every time the "With" statement is used, there's some Law of Demeter violation occurring. Long variable names might be a case for the With statement, but I find that the developer has to be careful not to let the With block get to big or it becomes hard to track which object makes up the "With context".
  • I don't use VB.NET (I used to use plain VB) but...

    Is the leading dot mandatory? If so, then I don't see a problem. In Javascript, the result of using with is that a property of an object looks just the same as a plain variable, and that is very dangerous, as you don't see if you're accessing a property or a variable, and thus, with is something to avoid.

    Not only is its use easier on the eyes, but for repeated access to properties of an object, it's likely to be faster, as the object is fetched through the method chain only once, and not once for every property.

    I do agree with other replies that you ought to avoid nested use of with, for the same reason as why to avoid with altogether in Javascript: because you no longer see what object your property belongs to.

    Stefan : The leading dot IS mandatory.
  • In practice, there are no really compelling points against it. I'm not a fan, but that's a personal preference, there's no empirical data to suggest that the With construct is bad.

    In .NET, it compiles to exactly the same code as fully-qualifying the object name, so there is no performance penalty for this sugar. I ascertained this by compiling, then disassembling, the following VB .NET 2.0 class:

    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
    

    The disassembly is as follows -- note that the calls to sb2's Append method look identical to the With statement calls for 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
    

    So if you like it, and find it more readable, go for it; there's no compelling reason not to.

    (By the way, Tom, I'm interested in knowing what happened with the debugger -- I can't recall ever seeing any unusual behavior in the debugger based on a With statement, so I'm curious to know what behavior you did see.)

    Tom : @John Rudy - Find the beginning of a With statement and set a breakpoint. Step to the next line (so you're hiding the first line right under the if block). Highlight it, then 'Add Watch'. You should see this: 'With' contexts and statements are not valid in debug windows.
    John Rudy : Ah, interesting! I don't frequently use Watches in debugging, which is why I never ran across it. I shall have to try that -- and maybe, for grins, try it across 2003, 2005 and 2008 and see if any of them behave differently. Thanks for the tip!
  • The 'with' is basically the 'cascade' from Smalltalk. It is a pattern in Kent Beck's Smalltalk Best Practice Patterns book.

    A summary of the pattern: use it when it makes sense to group the messages sent to the object. Don't use it if it just happens to be some messages sent to the same object.

0 comments:

Post a Comment