VB.NET‘ With’语句——接受还是回避?

在工作中,我经常参与一些项目,在这些项目中,某些对象的许多属性必须在其构建期间或其生命周期的早期设置。为了方便和可读性,我经常使用 With语句来设置这些属性。我发现

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

看起来比

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

用于仅设置属性的非常长的语句。

我注意到在调试时使用 With存在一些问题; 但是,我想知道是否有任何令人信服的理由避免在实践中使用 With?我总是假设通过编译器为上述两种情况生成的代码基本上是相同的,这就是为什么我总是选择编写我认为更具可读性的代码。

61451 次浏览

如果它使代码真正更具可读性,那就去做吧。如果它使 更少具有可读性,就要避免使用它——特别是,我建议您避免嵌套 With 语句。

C # 3.0只在对象初始化时有这个特性:

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

这不仅是 LINQ 非常需要的,而且在语法没有指示代码味道方面也是有意义的。我通常会发现,当我在一个对象上执行许多超出其初始构造的不同操作时,这些操作应该被封装为对象本身的一个操作。

关于你的例子,有一点需要注意——你真的需要“我”吗? 为什么不直接写:

PropertyA = True
PropertyB = "Inactive"

这里肯定有“我”的意思。

如果你有很长的变量名,最后会得到:

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

那么我将使用 WITH 来使它更具可读性:

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

在后一个示例中,性能甚至优于第一个示例,因为在第一个示例中,每次访问用户属性时,我都会获取用户,而在 WITH- 情况下,我只获取用户一次。

我可以得到性能增益,不使用,像这样:

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"

但是我会选择 WITH语句,它看起来更干净。

我只是把它作为一个例子,所以不要抱怨一个有很多关键字的类,另一个例子可能像: WITHRefundDialog。RefundDatagridView.SelectedRows (0)

我会怀疑使用很多 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"

那么你可能违反了 德米特尔法

我不使用 VB.NET (我过去常常使用普通的 VB) ,但是..。

前面的点是强制性的吗?如果是这样,那么我看不出有什么问题。在 Javascript 中,使用 with的结果是一个对象的属性看起来和一个普通变量一样,而 那个是非常危险的,因为你不知道你是在访问一个属性还是一个变量,因此,with是需要避免的。

不仅使用它更容易,而且对于对象的属性的重复访问,它可能更快,因为对象只通过方法链获取一次,而不是对每个属性都获取一次。

我确实同意其他的回复,你应该避免嵌套使用 with,原因和为什么在 Javascript 中完全避免使用 with一样: 因为你不再看到你的属性属于什么对象。

实际上,并没有什么真正令人信服的理由反对这种做法。我不是一个粉丝,但这只是个人偏好,没有经验数据表明 With的结构是不好的。

进去。NET 中,它将编译为与完全限定对象名称完全相同的代码,因此这个代码不会带来性能损失。我通过编译,然后反汇编,下面的 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

反汇编如下——注意,对 sb2Append方法的调用看起来与对 sbWith语句调用相同:

.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

因此,如果你喜欢它,并且觉得它更易读,那就去读吧; 没有什么令人信服的理由不去读。

(顺便说一下,汤姆,我很想知道调试器发生了什么——我不记得在调试器中看到过任何基于 With语句的异常行为,所以我很好奇你确实看到了什么行为。)

“ with”基本上是 Smalltalk 中的“瀑布”,它是 Kent Beck 的 Smalltalk 最佳实践模式一书中的一种模式。

模式的总结: 在对发送到对象的消息进行分组时使用它。如果它恰好是发送到同一对象的一些消息,则不要使用它。

这是关于可读性的,就像所有的语法糖一样,它可以是 过度使用

接受它 IF 你在几行内设置了一个对象的几个成员

With myObject
.Property1 = arg1
.Property2 = arg2
...

避免使用“ With”做任何其他事情

如果你写了一个 With 块,跨越50-100行,并涉及许多其他变量,它可以使真的很难记住什么是在块的顶部声明。出于显而易见的原因,我不会提供这种混乱代码的示例

使用 With 和对对象进行重复引用是有区别的,我认为这很微妙,但应该牢记在心。

使用 WITH语句时,它将创建一个引用该对象的新局部变量。使用。Xx 是对该本地引用的属性的引用。如果在 WITH语句的执行过程中,原始变量引用被更改,则 WITH引用的对象不会更改。考虑一下:

Dim AA As AAClass = GetNextAAObject()
With AA
AA = GetNextAAObject()


'// Setting property of original AA instance, not later instance
.SomeProperty = SomeValue
End With

因此,WITH语句不仅仅是语法上的“糖”,它实际上是一个不同的构造。虽然您不太可能像上面那样明确地编写代码,但是在某些情况下,这可能会在无意中发生,因此您应该注意到这个问题。最有可能出现的情况是,您可能正在遍历一个结构,例如一个对象网络,通过设置属性,这些对象的相互连接会被隐式地更改。

不惜一切代价避免使用 和布洛克(甚至可读性)。原因有二:

  1. Microsoft 关于... 的文档表示,在某些情况下,它会在堆栈上创建数据的副本,因此您所做的任何更改都将被丢弃。
  2. 如果您将其用于 LINQ 查询,则 lambda 的结果是 DO NOT Chain,因此每个中间子句的结果都会被丢弃。

为了描述这一点,我们从一本教科书中找到了一个(破碎的)例子,我的同事不得不向作者询问(这确实是不正确的,名称已经被更改以保护... ... 无论如何) :

用 dbcontext 什么的
. OrderBy (函数(currentBlah) currentBlah. LastName)
. then By (函数(currentBlah) currentBlah. FirstName)
。装载()
结束

OrderBy 和 Then By 根本就没有 无效。如果只删除 With 和 End With,并在前三行的末尾添加行继续字符,就可以重新格式化代码(如同一本教科书中的 15页之后所示)。

我们不需要更多的理由来使用 搜索并摧毁With 块,它们只在 翻译框架中有意义。

有一个问题,当使用它与结构,也就是你不能设置他们的字段,因为你正在工作的“与”表达式的一个本地副本(在输入时与块) ,而不是一个(副本的一个)对象引用在这种情况下:

ObjectExpression 的数据类型可以是任何类或结构类型 甚至是 VisualBasic 基本类型,如 Integer ObjectExpression 会导致除对象之外的任何结果,您可以 只读取其成员的值或调用方法,则您将获得一个 控件中使用的结构的成员分配值时,将出现错误 如果使用... EndWith 语句,则会得到相同的错误 调用返回结构并立即访问的方法 并为函数结果的成员赋值,例如 X = 1。两种情况下的问题都是结构 只存在于调用堆栈上,而且不可能修改 在这些情况下,结构成员可以写入一个位置,以便 程序中的任何其他代码都可以观察到变化。

在进入块时,对 objectExpression 进行一次计算 无法从 With 块中重新分配 objectExpression。

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

如果用语句传递结构名而不是返回结构的表达式,编译器可能会更聪明一些,但似乎并非如此