web-dev-qa-db-ja.com

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を使用することを回避する説得力のある理由があるかどうか疑問に思っていました?上記の2つのケースでコンパイラーを介して生成されたコードは基本的に同じであると常に想定していたため、読みやすいと感じるものを書くことを常に選択しました。

67
Tom

長い変数名があり、次のようになる場合:

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ケースではユーザーを1回しか取得しないため、後者の例では最初の例よりもパフォーマンスが向上しています。

次のように、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)

64
Stefan

実際には、それに対して本当に説得力のあるポイントはありません。私はファンではありませんが、それは個人的な好みです。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メソッドは、Withsbステートメント呼び出しと同一に見えます。

.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

あなたがそれを好きで、それがより読みやすいと思うならば、それのために行きなさい;しない理由はありません。

(ところで、 Tom 、デバッガで何が起こったのか知りたいです-Withステートメントに基づいてデバッガで異常な動作を見たことは今まで思い出せません、だから私はあなたが見た行動を知りたいです。)

24
John Rudy

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ステートメントは単なる構文上の砂糖ではなく、まったく別の構造です。上記のような明示的なコードをコーディングすることはほとんどありませんが、状況によってはこれが意図せずに発生する可能性があるため、問題に注意する必要があります。最も可能性の高い状況は、プロパティの設定によって相互接続が暗黙的に変更される可能性のあるオブジェクトのネットワークなどの構造をトラバースする場合です。

15
JohnRC

それはすべて読みやすさについてです。すべての構文糖と同様に、使いすぎの場合があります。

IFを受け入れるオブジェクトのいくつかのメンバーを数行にわたって設定している

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

回避「with」を使用して他の操作を実行する

50〜100行にまたがる多くの変数を含むWithブロックを記述すると、ブロックの先頭で宣言されたものを覚えるのが本当に難しくなります。明らかな理由により、このような厄介なコードの例は提供しません

11
phillihp

コードを本当に読みやすくするのであれば、それを試してください。 less読み取り可能にする場合は避けてください-特に、Withステートメントを入れ子にしないことをお勧めします。

C#3.0には、オブジェクトの初期化専用のこの機能があります。

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

これはLINQに必要なだけでなく、構文がコードの匂いを示さないという点でも意味があります。通常、オブジェクトに対して初期構築を超えてさまざまな操作を実行する場合、それらの操作はオブジェクト自体で単一の操作としてカプセル化する必要があります。

あなたの例についての1つのメモ-あなたは本当に「私」を本当に必要としますか?なぜ単に書かないのですか:

PropertyA = True
PropertyB = "Inactive"

?確かにその場合は「私」が暗示されています...

6
Jon Skeet

このキーワードを大量に使用するコードには疑いがあります:多数のインスタンス変数またはインスタンスプロパティを設定しやすくするために使用する場合、これはクラスが大きすぎることを示していると思われます( Large Class smell )。これを使用して、次のような呼び出しの長いチェーンを置き換える場合:

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"

あなたはおそらく違反しています Demeter Law

5
ljorquera

私はVB.NETを使用していません(私はプレーンVBを使用していました)が...

先頭のドットは必須ですか?もしそうなら、私は問題を見ません。 Javascriptでは、withを使用した結果、オブジェクトのプロパティはプレーン変数とまったく同じに見え、thatは非常に危険です。 'プロパティまたは変数にアクセスしているため、withは避けるべきものです。

目に使いやすいだけでなく、オブジェクトのプロパティに繰り返しアクセスする場合は、オブジェクトがすべてのプロパティに対して1回ではなくメソッドチェーンを通じてフェッチされるため、より高速になる可能性があります。

Javascriptでwithを完全に回避する理由と同じ理由で、withのネストされた使用を回避する必要があるという他の応答に同意します。 。

3
bart

「with」は、基本的にSmalltalkの「カスケード」です。これは、Kent BeckのSmalltalk Best Practice Patternsブックのパターンです。

パターンの要約:オブジェクトに送信されたメッセージをグループ化することが理にかなっている場合に使用します。同じオブジェクトに送信されたメッセージである場合は、使用しないでください。

3
soemirno

すべてのコストでWITHブロックを避けます(読みやすささえ)。 2つの理由:

  1. With ... End WithについてのMicrosoftドキュメント は、状況によってはスタック上にデータのコピーを作成するため、行った変更はすべて破棄されると述べています。
  2. LINQクエリに使用する場合、ラムダ結果はチェーンしないため、各中間句の結果は破棄されます。

これを説明するために、私たちの同僚が著者に質問しなければならなかった教科書の(壊れた)例があります(本当に間違っています、保護するために名前が変更されました...何でも):

Dbcontext.Blahsを使用
。OrderBy(Function(currentBlah)currentBlah.LastName)
。ThenBy(Function(currentBlah)currentBlah.FirstName)
。負荷()
で終わる

OrderByとThenByには、効果なしがあります。 WithおよびEnd Withのみをドロップし、最初の3行の最後に行継続文字を追加するだけでコードを再フォーマットすると...動作します(15ページ後同じ教科書で)。

WITHブロックを検索および破棄する必要はありません。それらは、Interpretedフレームワークでのみ意味がありました。

2
user4624979

「with」式のローカルコピー(with blockのエントリ時に作成された)で作業しており、(copyその場合のオブジェクト参照:

ObjectExpressionのデータ型は、任意のクラスまたは構造型、または整数などのVisual Basicの基本型です。 objectExpressionの結果がオブジェクト以外の場合、そのメンバーの値の読み取りまたはメソッドの呼び出しのみが可能で、With ... End Withステートメントで使用される構造体のメンバーに値を割り当てようとするとエラーが発生します。これは、構造を返すメソッドを呼び出し、すぐにアクセスして値を関数の結果のメンバーに割り当てた場合(GetAPoint()。x = 1など)に取得するエラーと同じです。どちらの場合でも問題は構造体は呼び出しスタック上にのみ存在し、これらの状況で変更された構造体メンバーが、プログラム内の他のコードが変更を監視できる場所に書き込む方法はありません。

ObjectExpressionは、ブロックに入るときに1回評価されます。 Withブロック内からobjectExpressionを再割り当てすることはできません。

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

構造体を返す式ではなく構造体名をwithステートメントに渡すと、コンパイラはもう少し賢くなったかもしれませんが、そうではないようです

1
George Birbilis