Mal wieder Dispose und Lebensdauer von Variablen

  • VB.NET

Es gibt 6 Antworten in diesem Thema. Der letzte Beitrag () ist von HalliHallo.

    Mal wieder Dispose und Lebensdauer von Variablen

    Hallo,
    ich beschäftige mich als Neuling in VB.NET (Umstieg von VB6) im Moment mit der Lebensdauer von Variablen.
    Nach vielem Lesen in Büchern und Foren bin ich nicht wirklich schlauer als vorher. ?(
    Soviel ich bisher verstanden habe soll alles was man "disposen" kann auch "disposed" werden. Alle anderen Sachen werden von alleine ungültig bei verlassen der Sub oder Schließen der Form.

    Aber ganz klar ist mir nicht wann und wie ich "disposen" muss oder kann.
    z.B. habe ich mir folgende Sub in einem Modul geschrieben um nicht bei jeder Combobox den ganzen Kram schreiben zu müssen.

    VB.NET-Quellcode

    1. Public Sub FuelleComboBox(cbo As ComboBox, sql As String, DispMember As String, ValMember As String)
    2. Dim DA As DbDataAdapter = PrFactory.CreateDataAdapter 'DbProviderFactories
    3. Dim DT As New DataTable
    4. DA.SelectCommand = PrFactory.CreateCommand
    5. DA.SelectCommand.Connection = FConn
    6. DA.SelectCommand.CommandText = sql
    7. DA.Fill(DT)
    8. cbo.DisplayMember = DispMember
    9. cbo.ValueMember = ValMember
    10. cbo.DataSource = DT
    11. DA.Dispose()
    12. 'DT.Dispose() geht nicht, sonst ist die Box leer.
    13. End Sub

    Eigenlich sollten doch der DbDataAdapter DA und die DataTable DT "Disposed" werden, oder? Beide habe eine Dispose-Methode. Wenn ich aber die DataTable dispose ist die ComboBox danach leer :(

    Wenn ich jetzt schreibe:

    VB.NET-Quellcode

    1. Public Sub FuelleComboBox(cbo As ComboBox, sql As String, DispMember As String, ValMember As String)
    2. Using DA As DbDataAdapter = PrFactory.CreateDataAdapter 'DbProviderFactories
    3. ......
    4. .....
    5. cbo.DataSource = DT
    6. end Using
    7. end sub

    dann funktioniert es und die Combobox bleibt gefüllt.
    Das bedeutet aber das der DataTable nicht "disposed" wird. Stimmt das?

    Und wenn das so ist, wann und wie muss ich DT "disposen". Ich komme ja nicht mehr ran.
    Ist ja innerhalb einer Sub definiert. Oder soll ich für die ganze Anwendung global den DataAdapter und die DataTable definieren und jedesmal die gleichen DA und DT benutzen. Dann könnte man bequem beim Beenden der Anwendung "disposen". Geht das überhaupt, kommen die sich nicht irgendwann in die Quere?

    Wenn das nicht geht, wäre es schön wenn mir jemand einen Tip geben könnte.

    Viele Grüße
    Gregor

    HalliHallo schrieb:

    dann funktioniert es und die Combobox bleibt gefüllt.
    Das bedeutet aber das der DataTable nicht "disposed" wird. Stimmt das?
    Ich wundere mich, dass dich das wundert.
    Hättest du gedacht, sie würde disposed? Warum hättest du das gedacht - steht da irgendwo etwas von DT.Dispose()? nicht? dann wird sie nicht disposed. doch? dann wird sie disposed.



    HalliHallo schrieb:

    Und wenn das so ist, wann und wie muss ich DT "disposen". Ich komme ja nicht mehr ran.
    Dispose die DT, wenn du sie nicht mehr brauchst.
    Wann brauchst du sie nicht mehr?
    Ich brauche die DT nicht mehr wenn die Form beendet wird, weil dann die combobox ja auch nicht mehr gebraucht wird :)
    Aber wie komme ich denn an DT wieder ran wenn ich den DatAdapter in einer Sub erstellt habe. Vor Verlassen der Sub kann ich ihn ja nicht disposen weil dann die combobox leer ist. Und wenn die sub beendet ist komme ich doch eigentlich nicht mehr ran oder? Oder verstehe ich gar nichts mehr?
    Die Lebensdauer von Variablen in .NET ist garnichtmal so einfach zu beschreiben. Die CLR darf Objekte länger am Leben lassen oder bereits früher in den Müll werfen... oder garnicht ;)

    VB.NET-Quellcode

    1. Sub Test()
    2. If Bedingung Then
    3. Dim A As New Dingsda
    4. 'Einige Zeilen Code
    5. A.Irgendwas()
    6. 'Mehr Code
    7. End If
    8. 'Noch mehr Code
    9. End Sub

    Die CLR kann das Objekt z.B. direkt nach A.Irgendwas() verwerfen, weil es nachher nie mehr verwendet wird (vorausgesetzt natürlich, dass das Objekt nicht noch von woanders referenziert wird). Oder sobald der If-Block verlassen wird. Oder sobald die Methode verlassen wird. Oder irgendwo dazwischen. Oder zu einer beliebigen Zeit später. Oder garnicht.
    Ich empfehle diese Serie: ericlippert.com/2015/05/18/whe…u-know-is-wrong-part-one/
    Ist zwar etwas lang, aber lohnt sich sehr, wenn man etwas in die Details von .NET gehen will.

    Deshalb empfehle ich, dass Du Dir darüber keine allzugroßen Gedanken machen solltest.
    Welche Objekte Du wann disposen solltest, steht in der Dokumentation (meistens jedenfalls ;) ) und hängt davon ab, was Du machen willst.
    Beispiel Bitmap aus Stream:

    VB.NET-Quellcode

    1. Dim SourceStream = System.IO.File.OpenRead("Pfad")
    2. Dim Bpm As New Bitmap(SourceStream)

    Solange Du die Bitmap verwenden möchtest, muss der Stream offen bleiben. Wenn Du die Bitmap der Image-Eigenschaft einer PictureBox zuweist, muss der Stream offen bleiben. Wenn Du später dann ein anderes Bild laden möchtest musst Du erst dann die alte Bitmap disposen, und den Stream auch:

    VB.NET-Quellcode

    1. Dim CurrentStream As Stream = Nothing
    2. Dim CurrentBitmap As Bitmap = Nothing
    3. Private Sub LoadBipm() Handles Button1.Click
    4. If CurrentBitmap IsNot Nothing Then
    5. 'Das würde die Bitmap ein zweites Mal disposen und ist deshalb unnötig, denn PictureBox1.Image beinhaltet ja das selbe Objekt wie CurrentBitmap.
    6. 'PictureBox1.Image.Dispose()
    7. 'Es ist aber sinnvoll, die Bitmap vor dem Disposen nicht mehr zu verwenden. Die PictureBox verhält sich glaub ich korrekt, wenn man ihr bereits verworfene Bitmaps gibt, aber korrekterweise sollte man die Verwendung vorher einstellen.
    8. 'Das ist insbesondere wichtig, wenn man im nachfolgenden Code nicht immer eine neue Bitmap erstellt und in der PictureBox anzeigt. Es ist generell keine gute Idee, verworfene Objekte im Programm herumliegen zu lassen
    9. PictureBox1.Image = Nothing
    10. CurrentBitmap.Dispose()
    11. CurrentStream.Dispose()
    12. End If
    13. CurrentStream = File.Open(...)
    14. CurrentBitmap = New Bitmap(CurrentStream)
    15. PictureBox1.Image = CurrentBitmap
    16. End Sub

    Achte hier darauf, dass Du die Objekte in der umgekehrten Reihenfolge verwirfst, in der Du sie erstellt hast. Wenn Du zuerst CurrentStream.Dispose() aufrufst, dann kann es Probleme geben, weil die Bitmap ja immer noch einen gültigen Stream benötigt.
    Manche Klassen sind so geschrieben, dass sie die Objekte, mit denen sie erstellt wurden, selbst disposen und man das nicht unbedingt nochmal selbst machen muss. Aber: Du solltest trotzdem. Ein Objekt mehrmals disposen tut nicht weh (davon muss der Autor der Klasse ausgehen) und es zeigt klar, dass das Objekt wirklich nicht mehr benötigt wird und verworfen werden soll.
    Andersrum ist aber doof: Wenn man z.B. den Stream noch benötigt, nachdem ein Objekt, das den Stream verwendet hat, verworfen wurde. Hier ein Beispiel dazu: Using und das Disposen von Streams
    Es sollte eigentlich in der Dokumentation (MSDN) stehen, was genau das Verhalten ist, aber manchmal ist die Dokumentation nicht ganz vollständig. Also wenn Du selbst mal eine Klasse schreibst, die IDisposable-Objekte entgegennimmt und auch selbst IDisposable implementiert, dann halte Dich vorzugsweise an folgende Konvention: Dispose nur Objekte, die Du selbst erstellt hast. Also der CryptoStream im Beispiel macht es genau falsch: Er ruft Dispose an einem Objekt auf, das er nicht selbst erstellt hat.

    Zu Deinem Beispiel oben:
    Der DataAdapter stellt einen Service zur Verfügung, mit dem Du eine DataTable füllen kannst. Sobald die DataTable gefüllt ist, brauchst Du den Service nicht mehr, denn die DataTable beinhaltet ja dann die Daten selbst.
    Aber: Die ComboBox speichert ihre Daten nicht selbst, wenn Du eine DataSource angibst. Die DataSource muss gültig sein, solange Daten angezeigt werden sollen. Deshalb kannst Du die DataTable noch nicht verwerfen, so wie man bei der PictureBox die Bitmap und den Stream nicht verwerfen darf, solange was angezeigt werden soll.
    Ich würde das Beispiel also so in der Richtung umschreiben:

    VB.NET-Quellcode

    1. Dim CurrentDataTable As DataTable = Nothing
    2. Sub LoadData()
    3. If CurrentDataTable IsNot Nothing Then
    4. ComboBox1.DataSource = Nothing
    5. CurrentDataTable.Dispose()
    6. End If
    7. Using Adapter = PrFactory.CreateDataAdapter
    8. Adapter.Foo = Bar
    9. CurrentDataTable = New DataTable
    10. Adapter.Fill(CurrentDataTable)
    11. ComboBox1.DataSource = CurrentDataTable
    12. End Using
    13. End Sub

    Hier ist klar ersichtlich, dass der Adapter nur kurzzeitig verwendet wird und dass die DataTable länger benötigt wird.


    Edit:
    Und Deine Frage in Post #3 müsste damit beantwortet sein. Du musst die DataTable irgendwo behalten. Sobald die Form geschlossen wird machst Du das, was in der LoadData-Methode am Anfang steht:

    VB.NET-Quellcode

    1. Sub LoadData()
    2. If CurrentDataTable IsNot Nothing Then
    3. Dim Temp = DirectCast(ComboBox1.DataSource, DataTable)
    4. ComboBox1.DataSource = Nothing
    5. Temp.Dispose()
    6. End If
    7. Using Adapter = PrFactory.CreateDataAdapter
    8. Adapter.Foo = Bar
    9. Dim Temp = New DataTable
    10. Adapter.Fill(Temp)
    11. ComboBox1.DataSource = Temp
    12. End Using
    13. End Sub
    14. Protected Overrides Sub OnClosing(e As FormClosingEventArgs)
    15. Dim Temp = DirectCast(ComboBox1.DataSource, DataTable)
    16. ComboBox1.DataSource = Nothing
    17. Temp.Dispose()
    18. End Sub


    Es gäbe natürlich die Möglichkeit, ComboBox1.DataSource zurückzucasten, statt in der Klasse CurrentDataTable zu deklarieren:

    VB.NET-Quellcode

    1. Protected Overrides Sub OnClosing(e As FormClosingEventArgs)
    2. ComboBox1.DataSource = Nothing
    3. CurrentDataTable.Dispose()
    4. End Sub

    Aber das ist keine schöne Lösung.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils

    Niko Ortner schrieb:

    Und Deine Frage in Post #3 müsste damit beantwortet sein. Du musst die DataTable irgendwo behalten.
    naja, wenn sie in der Combo angezeigt ist, dann ist sie doch behalten - da muss man eiglich nix extras mehr machen.
    Und wie man sone immanent behaltene DataTable disposed, zeigst du ja auch bereits, in deim vorletzten Listing, zeilen#15 - 19.
    Es ginge halt noch knapper:

    VB.NET-Quellcode

    1. Protected Overrides Sub OnClosing(e As FormClosingEventArgs)
    2. DirectCast(ComboBox1.DataSource, DataTable).Dispose()
    3. End Sub




    Insgesamt hängt da aber auch die Architektur-Frage der ganzen Anwendung dranne: Ist zB. imo bereits schlechtes Design, überhaupt an irgendeiner Stelle mit new DataTable() zu arbeiten.
    Weil was man (an Disposablem) mit New() sich erzeugt, das muss man iwann auch mit Dispose() wieder entsorgen.

    Stattdessen ist imo viel besser, sich ein typisiertes Dataset im DatasetDesigner zurechtzumachen, und davon dann auch zur Laufzeit eines zu erstellen - nur eines!
    Und dann nirgendwo mehr ein new DataTable(), sondern die DataTables sind alle fertig da, von Anfang an, und man soll die benutzen, die da sind, statt immer mehr Kinder in die Welt zu setzen, die man dann auch wieder aufräumen muss.

    Wenn man sich an dieses Singleton-DataSource-Konzept hält - ist die Dispose-Problematik auf einmal komplett verschwunden: Es gibt nur ein Dataset, und das wird nie disposed. Nichtmal wenn die Anwendung schließt, muss mans disposen, denn das Betriebssystem räumt dann ja eh das ganze Proggi aussm Speicher.

    Soweit die Theorie - ich kann auch mit ganz konkreten Fallbeispielen aufwarten, wie man Datasetse zurechtmacht, und auch Databinding zurechtmacht:
    vier Views-Videos
    Mit "Zurechtmachen" ist gemeint, dass Dataset und Databinding (und die Controls ja sowieso) im (Form-)Designer zurechtgemacht (engl: "designed") werden - da werden keine zig-hundert Zeilen Code in den Editor getippt.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „ErfinderDesRades“ ()