André Krämers Blog

Lösungen für Ihre Probleme

Wie in meinem letzten Blog-Post geschrieben, arbeite ich derzeit nach langer Abstinenz wieder an einigen VB.NET Projekten. Dabei stieß ich auch auf den “With-Block”, einen alten Bekannten aus VB 6 Zeiten, von dem in diesen Projekten intensiver Gebrauch gemacht wurde. Ein solches Konstrukt erlaubt es innerhalb eines Blocks ein Objekt anzugeben, auf das alle Statements ausgeführt werden, die nicht näher qualifiziert werden. So kann man einiges an Tipp-Arbeit sparen. Folgendes Beispiel soll dies verdeutlichen:

Sub Main()
Dim p As Person = New Person
With p
.Name = "Mueller"
.Age = 29
Console.WriteLine("Name:{0}, Alter:{1}", .Name, .Age)
EndWith
Console.Read()
EndSub

So gerne ich das WithEnd With auch unter VB 6 genutzt hatte, so zuwider war mir nach nun mehr als sechs Jahren C# die Nutzung unter VB.NET. Ich hatte das Gefühl, dass mein Code durch den Einsatz des “With-Blocks” Struktur und Lesbarkeit verlor und eine Hintertür für unnötige Fehlerquellen geöffnet wird.

Bestätigt wurde mein Gefühl als ich Zeilen in folgender Art las:

Sub Main()
Dim p As Person = New Person
With p
  .Name = "Mueller"
  .Age = 29
  p = New Person
p.Age = 21
p.Name = "Meier"
Console.WriteLine("Name:{0}, Alter:{1}", .Name, .Age)  ' Wird hier nun Mueller oder Meier ausgegeben?
Console.WriteLine("Name:{0}, Alter:{1}", p.Name, p.Age) ' Gleicht die Ausgabe dieser Zeile der vorherigen?
End With
Console.Read()
End Sub

An dieser Stelle war sich das gesamte Team unsicher, welchen Effekt die Zeile p = new Person innerhalb des Blocks haben wird. Es herrschte rege Diskussion, ob nur die Anweisungen, die über “p.” qualifiziert werden das neue Objekt verändern, oder ob auch die Anweisungen die nur über den “.” qualifiziert werden das neue Objekt verändern.

Um die Antwort vorweg zu nehmen: Wir haben nun zwei Objekte von der Klasse Person im Speicher, die beide referenziert werden. Das ursprüngliche Objekt wird durch “.” angesprochen, dass neu erstelle Objekt durch “p.”.

Sehr schön sieht man das ganze auch wenn man sich den zugehörigen IL-Code ansieht:

.method publicstatic void Main() cil managed
{
  .custom instance void [mscorlib]System.STAThreadAttribute::.ctor()         .entrypoint         .maxstack 3         .locals init (             [0] class WithTest.Person p,             [1] class WithTest.Person VB$t_ref$L0)         L_0000: nop          L_0001: newobj instance void WithTest.Person::.ctor()         L_0006: stloc.0          L_0007: ldloc.0          L_0008: stloc.1          L_0009: ldloc.1          L_000a: ldstr "Mueller"         L_000f: callvirt instance void WithTest.Person::set_Name(object)         L_0014: nop          L_0015: ldloc.1          L_0016: ldc.i4.s 0x1d         L_0018: box int32         L_001d: callvirt instance void WithTest.Person::set_Age(object)         L_0022: nop          L_0023: newobj instance void WithTest.Person::.ctor()         L_0028: stloc.0          L_0029: ldloc.0          L_002a: ldstr "Meier"         L_002f: callvirt instance void WithTest.Person::set_Name(object)         L_0034: nop          L_0035: ldloc.0          L_0036: ldc.i4.s 0x15         L_0038: box int32         L_003d: callvirt instance void WithTest.Person::set_Age(object)         L_0042: nop          L_0043: ldstr "Name:{0}, Alter:{1}"         L_0048: ldloc.1          L_0049: callvirt instance object WithTest.Person::get_Name()         L_004e: callobject [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::GetObjectValue(object)         L_0053: ldloc.1          L_0054: callvirt instance object WithTest.Person::get_Age()         L_0059: callobject [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::GetObjectValue(object)         L_005e: call void [mscorlib]System.Console::WriteLine(string, object, object)         L_0063: nop          L_0064: ldstr "Name:{0}, Alter:{1}"         L_0069: ldloc.0          L_006a: callvirt instance object WithTest.Person::get_Name()         L_006f: callobject [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::GetObjectValue(object)         L_0074: ldloc.0          L_0075: callvirt instance object WithTest.Person::get_Age()         L_007a: callobject [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::GetObjectValue(object)         L_007f: call void [mscorlib]System.Console::WriteLine(string, object, object)         L_0084: nop          L_0085: ldnull          L_0086: stloc.1          L_0087: call int32 [mscorlib]System.Console::Read()         L_008c: pop          L_008d: nop          L_008e: ret      }

Wie man in den Zeilen 6 - 8 sieht werden zunächst zwei Variablen vom Typ Person auf dem Stack abgelegt. Die in unserem VB.NET SourceCode deklarierte Variable p wird an der Index Position 0 des Stacks abgelegt (Zeile 7) und zusätzlich wird eine Variable VB$t_ref$L0 an der Position 1 abgelegt. Letzte Variable wird später zur Umsetzung unseres “With-Blocks” genutzt. In der Zeile 10 wird nun ein neues Objekt der Klasse Person erstellt und auf dem Evaluationsstack abgelegt.

In der Zeile 11 wird dieses Objekt der lokalen Variablen am Index 0, also unserer Variablen p, zugewiesen. In der Zeile 12 wird der Inhalt der Variablen am Index 0, also unser Personen Objekt, wieder auf den Evaluationsstack geladen, um in Zeile 13 schließlich der Variablen am Index 1 (VB$t_ref$L0) zugewiesen zu werden.

In den Zeilen 15 - 22 werden anschließend die Werte für den Namen und das Alter auf dem Stack abgelegt und die entsprechenden Setter-Methoden des durch die Variable am Index 1 (VB$t_ref$L0) referenzierten Objektes aufgerufen.

Bis hier hin ist die Welt noch in Ordnung. Wir haben zwei Variablen vom Typ Person, die beide das selbe Objekt referenzieren. Die Freude währt jedoch nur bis zur Zeile 23. In dieser Zeile wird nämlich ein neues Objekt vom Typ Person erstellt und in den folgenden Zeilen der lokalen Variablen am Index 0 (p) zugewiesen. Ab diesem Zeitpunkt greifen die Anweisungen, die über “.” und die, die über “p.” qualifizieren auf verschiedene Objekte zu!

Welche Fehler sich in solch einem Szenario bei kleinen Unachtsamkeiten  einschleichen können, liegt auf der Hand.

Daher lautet mein persönliches Fazit:

Die Zeit, die ich durchs Tippen beim Einsatz des “With-Blocks” spare, büße ich während der Fehlersuche unter Umständen mehrfach wieder ein. Daher lautet meine Devise für die Zukunft: Finger weg von “with… end with”.

Kommentare willkommen :-)

Es gibt 2 Kommentare

Comment by Tom Inm
Von Tom Inm | 29.10.2011 21:11
Sorry, aber wer so den with- Block verwendet, der gehört wirklich mit Fehlern bestraft ;). Weder in VB 6 noch in .NET sollte man jeweils innerhalb des with-Blocks, außer der Zuweisung von Werten des angegeben Objektes im with Block, Code- Zeilen stellen.
Comment by André Krämer
Von | 29.10.2011 21:11
Da gebe ich dir uneingeschränkt recht. Wenn man den With-Block nutzt, dann nur in der von dir beschriebenen Art :-)