72

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

4

10 回答 10

69

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

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 语句,它看起来更干净。

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

于 2008-11-12T12:38:30.860 回答
24

在实践中,没有真正令人信服的观点反对它。我不是粉丝,但这是个人喜好,没有经验数据表明该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

反汇编如下 - 请注意,对sb2'Append方法的调用看起来与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语句的异常行为,所以我很想知道你看到了什么行为.)

于 2008-11-12T14:22:04.943 回答
15

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

于 2013-08-17T11:01:18.323 回答
13

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

如果您在几行上设置对象的多个成员,请拥抱它

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

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

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

于 2012-04-05T13:09:34.267 回答
6

如果它使代码真正更具可读性,那就去做吧。如果它使其可读性降低,请避免使用它 - 特别是,我建议您避免嵌套 With 语句。

C# 3.0 具有此功能,仅用于对象初始化:

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

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

关于你的例子的一个注释 - 你真的需要“我”吗?为什么不直接写:

PropertyA = True
PropertyB = "Inactive"

? 当然,在这种情况下,“我”是隐含的……

于 2008-11-12T12:24:01.810 回答
5

我会怀疑使用大量 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"

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

于 2008-11-12T12:55:33.197 回答
3

我不使用 VB.NET(我曾经使用普通的 VB)但是......

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

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

我确实同意您应该避免嵌套使用的其他回复,原因与在 Javascript 中完全避免使用的with原因相同with:因为您不再看到您的属性属于哪个对象。

于 2008-11-12T14:09:48.260 回答
3

“with”基本上是 Smalltalk 的“级联”。它是 Kent Beck 的 Smalltalk Best Practice Patterns 书中的一个模式。

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

于 2009-11-12T15:31:10.273 回答
2

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

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

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

With dbcontext.Blahs
.OrderBy(Function(currentBlah) currentBlah.LastName)
.ThenBy(Function(currentBlah) currentBlah.FirstName)
.Load()

OrderBy 和 ThenBy 完全没有效果。如果您仅通过删除 With 和 End With 来重新格式化代码,并在前三行的末尾添加行继续字符......它可以工作(如同一教科书后面的 15 页所示)。

我们不再需要任何理由来搜索和销毁WITH 块。它们只在解释框架中有意义。

于 2015-09-24T15:45:37.427 回答
1

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

objectExpression 的数据类型可以是任何类或结构类型,甚至可以是 Visual Basic 基本类型,例如 Integer。如果 objectExpression 生成的不是对象,则只能读取其成员的值或调用方法,如果尝试将值分配给 With...End With 语句中使用的结构的成员,则会出现错误。如果您调用一个返回结构并立即访问并将值分配给函数结果成员的方法(例如 GetAPoint().x = 1),您将遇到相同的错误。这两种情况下的问题是结构仅存在于调用堆栈上,在这些情况下,修改后的结构成员无法写入某个位置,以便程序中的任何其他代码都可以观察到更改。

objectExpression 在进入块时被评估一次。您不能从 With 块中重新分配 objectExpression。

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

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

于 2017-08-17T14:39:48.467 回答