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.
-
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 avoidwith
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
'sAppend
method look identical to theWith
statement calls forsb
:.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