在工作中,我经常从事一些项目,其中某些对象的许多属性必须在其构建过程中或在其生命周期的早期进行设置。为了方便和可读性,我经常使用 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 在实践中?我一直认为通过编译器为上述两种情况生成的代码基本上是相同的,这就是为什么我总是选择编写我认为更具可读性的代码。

有帮助吗?

解决方案

如果你有很长的变量名并且最终会得到:

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-情况下,我只获取用户一次。

我可以在不使用 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语句,它看起来更干净。

我只是以此为例,所以不要抱怨有很多关键字的类,另一个例子可能是这样的:带 RefundDialog.RefundDatagridView.SelectedRows(0)

其他提示

在实践中,并没有真正令人信服的理由反对它。我不是粉丝,但这是个人喜好,没有经验数据表明 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 方法看起来与 With 声明呼吁 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

因此,如果您喜欢它,并且发现它更具可读性,那就去吧;没有令人信服的理由不这样做。

(顺便一提, 汤姆, ,我有兴趣了解调试器发生了什么——我不记得曾经在调试器中看到过任何基于以下内容的异常行为: With 声明,所以我很想知道你看到了什么行为。)

使用 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 语句不仅仅是语法糖,它确实是一个不同的结构。虽然您不太可能编写像上面这样的显式代码,但在某些情况下,这可能会无意中发生,因此您应该意识到这个问题。最可能的情况是您可能正在遍历一个结构,例如对象网络,其互连可以通过设置属性隐式更改。

这一切都与可读性有关。像所有语法糖一样,它可以是 过度使用.

拥抱它如果 您正在通过几行设置对象的多个成员

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

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

如果您编写一个跨 50-100 行并涉及许多其他变量的 With 块,那么它可能会让人很难记住块顶部声明的内容。出于显而易见的原因,我不会提供这种混乱代码的示例

如果它确实使代码更具可读性,那就去做吧。它在哪里制造 较少的 可读,避免它 - 特别是,我建议您避免嵌套 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"

那么你可能违反了 德米特定律

我不使用 VB.NET(我曾经使用纯 VB)但是...

前导点是强制性的吗?如果是这样,那么我认为没有问题。在Javascript中,使用的结果 with 对象的属性看起来与普通变量一样,并且 非常危险,因为您看不到您是否正在访问属性或变量,因此, with 是要避免的事情。

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

我确实同意其他回复,您应该避免嵌套使用 with, ,与为什么要避免的原因相同 with 全部用 JavaScript 编写:因为你不再看到你的财产属于哪个对象。

“with”基本上是来自 Smalltalk 的“cascade”。它是 Kent Beck 的 Smalltalk 最佳实践模式书中的一个模式。

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

避免 带块 不惜一切代价 (甚至可读性)。两个原因:

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

为了描述这一点,我们有一个来自教科书的(损坏的)例子,我的同事不得不向作者询问(这确实不正确,名称已被更改以保护......任何):

使用 dbcontext.Blahs
.OrderBy(函数(currentBlah) currentBlah.LastName)
.ThenBy(函数(currentBlah) currentBlah.FirstName)
。加载()
结束于

OrderBy 和 ThenBy 有 没有效果 根本不。如果您通过仅删除 With 和 End With 并在前三行末尾添加行继续字符来重新格式化代码...它有效(如图所示 15页后 在同一本教科书中)。

我们不需要更多的理由 搜索和摧毁 带块。它们只在某种情况下才有意义 解释 框架。

将其与结构一起使用时存在一个问题,即您无法设置它们的字段,因为您正在处理“with”表达式的本地副本(在进入 with 块时制作),而不是使用 (copy在这种情况下的 an) 对象引用:

对象表达的数据类型可以是任何类或结构类型,甚至可以是整数等视觉基本基本类型。如果 objectExpression 会生成对象以外的任何内容,您可以 仅读取其成员或调用方法的值,即可获得 如果尝试将值分配给 跟。。。End With 语句。如果您遇到以下情况,则会遇到相同的错误 调用返回结构并立即访问的方法 并为函数结果的成员赋值,例如 GetAPoint().x = 1。这两种情况下的问题在于结构 只存在于调用栈上,没有办法修改 在这些情况下,结构成员可以写入一个位置,以便 程序中的任何其他代码都可以观察到更改。

objectExpression 在进入块时计算一次。您不能从块内重新分配对象表达。

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

猜测如果您将结构名称传递给 with 语句而不是返回结构的表达式,编译器可能会更聪明,但似乎并非如此

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top