Frage zur Instanzierung einer ObservableCollection

  • VB.NET

Es gibt 16 Antworten in diesem Thema. Der letzte Beitrag () ist von eichseinet.

    Frage zur Instanzierung einer ObservableCollection

    Hallo zusammen!

    Ich hab da ein Verständnisproblem mit einem Beispiel aus einem Video. (youtube.com/watch?v=KG6zxlljCsM)
    Inzwischen entstand daraus ein funktionierendes kleines Beispielprojekt. Es gibt eine Klasse, in der alle ObservableCollections erstellt werden. Von dieser Klasse wird in einem Modul eine Instanz erstellt. So lässt sich aus dem gesamten Programm darauf zugreifen.
    Was aber passiert, wenn man die Collection wie folgt erstellt?

    VB.NET-Quellcode

    1. Public Class Collection_Klasse
    2. Private _lesen_coll As New ObservableCollection(Of Lesen_Klasse) 'mit NEW
    3. Public Property Lesen_Coll() As ObservableCollection(Of Lesen_Klasse)
    4. Get
    5. Return _lesen_coll
    6. End Get
    7. Set(ByVal value As ObservableCollection(Of Lesen_Klasse))
    8. _lesen_coll = value
    9. End Set
    10. End Property


    Im Modul wird dann so eine Instanz der Klasse erstellt.

    VB.NET-Quellcode

    1. Public DC As New Collection_Klasse


    Führt das NEW (also die Instanzierung) im "Speicherbereich" der Collection nicht dazu, dass bei jeder Änderung eines Propertys eine neue Instanz entsteht? Falls das so ist, füllt sich dann nicht der Arbeitsspeicher immer weiter, oder wird die alte Instanz hier einfach wieder von .NET aus dem Speicher geworfen?
    Oder entsteht nur eine neue Instanz des Datenbereich der Collection, wenn die Klasse an einer weiteren Stelle noch einmal instanziert wird?

    Edit: oder passiert dabei im Grunde das Gleiche wie bei dieser Methode?

    VB.NET-Quellcode

    1. Public Class Collection_Klasse
    2. Private _lesen_coll As ObservableCollection(Of Lesen_Klasse) 'OHNE NEW
    3. Public Property Lesen_Coll() As ObservableCollection(Of Lesen_Klasse)
    4. Get
    5. Return _lesen_coll
    6. End Get
    7. Set(ByVal value As ObservableCollection(Of Lesen_Klasse))
    8. _lesen_coll = value
    9. End Set
    10. End Property


    und im Programm dann:

    VB.NET-Quellcode

    1. Lesen_Coll = new ObservableCollection(Of Lesen_Klasse)


    Im 1. Beispiel entsteht dann eine neue Instanz des Speicherbereichs durch Instanzierung der Klasse, die die Collection enthält. Beim 2. Beispiel wird der Speicherbereich einfach direkt mit einer neuen Instanz befüllt? Verhält es sich so?

    Gruß
    eddi

    Thema verschoben (da nicht nur für die WPF relevant) ~VaporiZed

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „VaporiZed“ ()

    @eichseinet Bei jedem Setter-Aufruf wird die vorhandene Instanz mit der neuen Instanz oder Nothing überschrieben.
    Wenn Deine Klasse Lesen_Klasse IDisposable implementiert, musst Du vor dem Überschreiben alle Elemente der ObservableCollection-Instanz disposen, sofern sie ausschließlich in dieser Collection enthalten sind:

    VB.NET-Quellcode

    1. Set(ByVal value As ObservableCollection(Of Lesen_Klasse))
    2. If _lesen_coll IsNot Nothing AndAlso _lesen_coll.Count > 0 Then
    3. For i = 0 To _lesen_coll.Count - 1
    4. _lesen_coll[i].Dispose()
    5. Next
    6. End If
    7. _lesen_coll = value
    8. End Set
    Ansonsten musst Du nix tun, das übernimmt der Garbage-Collector für Dich.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    VB-Fragen über PN / Konversation werden ignoriert!
    @eichseinet Das gilt für beide.
    Deswegen wird ja in meinem Snippet die vorhandene Variable _lesen_coll auf Existenz getestet.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    VB-Fragen über PN / Konversation werden ignoriert!
    Ich würde dringend empfehlen, den Property-Setter zu entfernen.
    Eine ObservableCollection auszutauschen ist oft verheerend, weil vielfach daran gebunden ist, und die Bindungen springen ja nicht von der alten OC auf die neue um.
    Am besten auch das Backing-Field Readonly:

    VB.NET-Quellcode

    1. Public Class Collection_Klasse
    2. Private ReadOnly _lesen_coll As New ObservableCollection(Of Lesen_Klasse)
    3. Public ReadOnly Property Lesen_Coll() As ObservableCollection(Of Lesen_Klasse)
    4. Get
    5. Return _lesen_coll
    6. End Get
    7. End Property
    So, das ist kurz, und kann nix anbrennen
    Auch brauch man nicht auf Existenz testen. Die Existenz ist sichergestellt, und kann sich wg. Readonly auch nix dran ändern.
    Auch um Dispose der Elemente (wenn sie denn IDisposable sind) braucht man sich im Setter nicht kümmern - einfach weils keinen Setter gibt (genial, oder?).

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

    Kann mir bitte mal jemand erklären warum das überhaupt funktioniert? ;(
    Wieso kann man einem Objekt das als "readonly" angelegt ist etwas hinzufügen und Werte ändern? Beim Lesen dacht ich noch, das klappt dann nur noch aus der Tabelle, die dran gebunden ist. Aber nix da! Auch per Button lassen sich Items hinzufügen und Werte in der OC ändern. ?(
    OK. Bisher war ich der Meinung, das Backing-Field wäre der Inhalt der Collection.
    Jetzt stellt sich aber die Frage, was lässt sich denn konkret mit der Collection anfangen, wenn sie nicht readonly angelegt ist?
    Bei meinem aktuellen Wissensstand fällt mir nciht viel mehr ein als eben Daten ändern bzw. hinzuzufüge/entfernen.

    Außerdem gibt da noch etwas; Bisher stand im Setter immer "raiseEvent Property changed", um die Oberfläche auch über Änderungen zu informieren. Ohne kam in der DataGrid keine Änderung an. Mit dieser für mich neuen Methode (Collection in einer extra Klasse + Instanz der Klasse) ist das nicht mehr nötig. Das Projekt wurde nach dem Video angefertigt. Am Ende fiel auf, dass nur in der Klasse aus der die Collection besteht ein solches Ivent gefeuert wird. Das war sonst auch innerhalb der Collection nötig.

    Edit: hier mal der Code des Projekts
    eine der beiden Klassen
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Imports System.ComponentModel
    2. Imports System.Runtime.CompilerServices
    3. Public Class Lesen_Klasse
    4. Implements INotifyPropertyChanged
    5. Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    6. Private Sub raisepropertychanged(<CallerMemberName> Optional ByVal prop As String = "")
    7. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(prop))
    8. End Sub
    9. Sub New()
    10. End Sub
    11. Sub New(ByVal zeiledatenquelle As Integer)
    12. Zeile_Daten_Quelle = zeiledatenquelle
    13. End Sub
    14. 'Nummerierung der Tabelle
    15. Private _id As Integer
    16. Public Property ID() As Integer
    17. Get
    18. Return _id
    19. End Get
    20. Set(ByVal value As Integer)
    21. _id = value
    22. raisepropertychanged()
    23. End Set
    24. End Property
    25. 'Vorgabe welcher Index aus der Daten_Quelle gelesen werden soll
    26. Private _zeile_daten_quelle As Integer
    27. Public Property Zeile_Daten_Quelle() As Integer
    28. Get
    29. Return _zeile_daten_quelle
    30. End Get
    31. Set(ByVal value As Integer)
    32. _zeile_daten_quelle = value
    33. raisepropertychanged()
    34. End Set
    35. End Property
    36. 'der aus Daten_Quelle gelesene Wert
    37. Private _gelesener_wert As String
    38. Public Property Gelsener_Wert() As String
    39. Get
    40. Return _gelesener_wert
    41. End Get
    42. Set(ByVal value As String)
    43. _gelesener_wert = value
    44. raisepropertychanged()
    45. End Set
    46. End Property
    47. End Class


    Klasse, die alle Collections enthält
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Imports System.Collections.ObjectModel
    2. Public Class Collection_Klasse
    3. Private _lesen_coll As New ObservableCollection(Of Lesen_Klasse)
    4. Public Property Lesen_Coll() As ObservableCollection(Of Lesen_Klasse)
    5. Get
    6. Return _lesen_coll
    7. End Get
    8. Set(ByVal value As ObservableCollection(Of Lesen_Klasse))
    9. _lesen_coll = value
    10. End Set
    11. End Property
    12. Private _daten_quelle_coll As New ObservableCollection(Of Daten_Quelle_Klasse)
    13. Public Property Daten_Quelle_Coll() As ObservableCollection(Of Daten_Quelle_Klasse)
    14. Get
    15. Return _daten_quelle_coll
    16. End Get
    17. Set(ByVal value As ObservableCollection(Of Daten_Quelle_Klasse))
    18. _daten_quelle_coll = value
    19. End Set
    20. End Property
    21. End Class


    Das Modul in dem eine Instanz der Klasse mit Collections erstellt wird
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Namespace DCM
    2. Module DatenContainer_Modul
    3. Public DC As New Collection_Klasse
    4. End Module
    5. End Namespace


    Datenkontext eines UserControls setzen
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Private Sub Tab01_UserControl_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
    2. Me.DataContext = DC.Lesen_Coll
    3. End Sub


    und zuletzt der XAML des UserControls
    Spoiler anzeigen

    XML-Quellcode

    1. <UserControl x:Class="Tab01_UserControl"
    2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    4. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    5. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    6. xmlns:local="clr-namespace:Klassen_Global"
    7. mc:Ignorable="d" d:DataContext="{d:DesignInstance IsDesignTimeCreatable=False, Type={x:Type local:Lesen_Klasse}}"
    8. d:DesignHeight="450" d:DesignWidth="800">
    9. <Grid>
    10. <DataGrid ItemsSource="{Binding}" AutoGenerateColumns="False">
    11. <DataGrid.Columns>
    12. <DataGridTextColumn Header="ID" Binding="{Binding ID}"/>
    13. <DataGridTextColumn Header="Index lesen Datenquelle" Binding="{Binding Zeile_Daten_Quelle}" ToolTipService.ToolTip="Dieser Index wird aus der anderen Collection gelesen"/>
    14. <DataGridTextColumn Header="gelesener Wert" Binding="{Binding Gelsener_Wert}"/>
    15. </DataGrid.Columns>
    16. </DataGrid>
    17. </Grid>
    18. </UserControl>


    bisher wurden die Collections direkt im CodeBehind wie folgt angelegt (das Folgende gehört so nicht zu dem oben gezeigten Projekt!):

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Private _lesen_coll As ObservableCollection(Of Lesen_Klasse)
    2. Public Property Lesen_Coll() As ObservableCollection(Of Lesen_Klasse)
    3. Get
    4. Return _lesen_coll
    5. End Get
    6. Set(ByVal value As ObservableCollection(Of Lesen_Klasse))
    7. _lesen_coll = value
    8. raisepropertychanged 'führt dann in der sub wieder das aus: RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(prop))
    9. End Set
    10. End Property


    Fehlte hier der Aufruf des RaiseEvent PropertyChanged, dann gabs auch keine Aktualisierung auf der Oberfläche.
    Beim oben gezeigten Projekt ist das aber nicht mehr nötig.

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „eichseinet“ ()

    eichseinet schrieb:

    Jetzt stellt sich aber die Frage, was lässt sich denn konkret mit der Collection anfangen, wenn sie nicht readonly angelegt ist?
    Komische Frage.
    Dein Code zeigt doch genau Collections, die nicht readonly angelegt sind - also was kannst du damit anfangen?

    Mein Punkt ist, dass du genau dasselbe auch mit Collections anfangen kannst, deren BackingFiels Readonly deklariert ist, und deren Properties ebenfalls.
    Ergibt nur halb soviel Code, und funktioniert sicherer.
    Den Setter brauchst du nicht, weil du willst ihn nie benutzen. Also weg damit, dann kann auch nicht passieren, dass iwann ein Töffel ihn doch benutzt.
    Ich glaub ich versteh's jetzt. Es wird ja ein Property vom Typ ObservableCollection angelegt. Ein Property hat aber grundsätzlich erst mal einen Setter. Legt man es aber vom Typ ObservableCollection an, dann ist der Setter einfach für diesen Typ unnötig.
    Mich hat es total verwundert, warum man ein Objekt in VB.NET einbaut, das mehr "kann" als man nutzen wird/soll. Aber das Property als "übergeordnete Ebene" für weitere Typen, die halt doch einen Setter brauchen hab ich nicht bedacht.

    Danke

    eichseinet schrieb:

    Ich glaub ich versteh's jetzt.
    hmm

    eichseinet schrieb:

    Es wird ja ein Property vom Typ ObservableCollection angelegt. Ein Property hat aber grundsätzlich erst mal einen Setter.
    Was heisst "wird angelegt"? Du hast es doch hingeschrieben, oder?
    Hättest es ja ebensogut ohne Setter hinschreiben können.

    eichseinet schrieb:

    Legt man es aber vom Typ ObservableCollection an, dann ist der Setter einfach für diesen Typ unnötig.
    Das liegt nicht am Typ, dass hier der Setter unnötig ist, sondern es liegt daran, wofür du die Property benötigst.
    Musst du einer Property etwas zuweisen, braucht sie logischerweise einen Setter. Musst du ihr niemals etwas zuweisen (und das ist hier der Fall), verbietet sich der Setter gradezu.

    Natürlich können auch Fälle auftreten, wo einer Property vom Typ ObservableCollection eine andere ObservableCollection zugewiesen werden soll - dann braucht sie wie gesagt einen Setter. Ist aber hier nicht der Fall.
    Hier wird eine Property benötigt, deren Wert (eine ObservableCollection) von Anfang an da ist, und die niemals geändert oder auf Nothing gesetzt werden soll - Readonly eben.
    Ebenso das BackingField der Property: Soll von Anfang an eine ObservableCollection enthalten, und das BackingField soll auch niemals geändert werden - Readonly eben.

    Haste deinen Code nun diesbezüglich geändert?

    eichseinet schrieb:

    Ein Property hat aber grundsätzlich erst mal einen Setter.

    Ja, ich habs in meinem Beispiel selbst hingeschrieben. Der zitttierte Satz sollte ausdrücken, dass ein Property selbst in der kurzen schreibweise mit einer Zeile im Hintergrund automatisch einen Setter anlegt. (so hab ich's zumindest bei Microsoft gelesen)
    Der fehlt wohl nur, wenn man die längere Schreibweise wählt und eben keinen Setter hinschreibt.
    Dein letzter Beitrag beantwortet jetzt auch die Frage, was man denn nun mit einem Property zusätzlich anstellen kann, ohne den Schreibschutz. Ne andere Collection oder auch Nothing zuweisen. Da hast du recht; das brauch ich nicht.
    Und ja, mein Code wird entsprechend angepasst.

    Danke für die Hilfe!

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

    So schnell kann's gehn... Wieder was dazu gelernt.
    Liest man eine Collection über einen Serializer ein, dann wird der Property offensichtlich eine neue Instanz der Collection zugewiesen. Also kann diese doch nicht als readonly angelegt werden.

    VB.NET-Quellcode

    1. Seriendings = New XmlSerializer(GetType(ObservableCollection(Of Lesen_Klasse)))
    2. Leser = New IO.StreamReader("lesen.xml")
    3. DC.Lesen_Coll = Seriendings.Deserialize(Leser)
    4. Leser.Dispose()
    5. Lesen_View = CollectionViewSource.GetDefaultView(DC.Lesen_Coll)
    6. Me.DataContext = Lesen_View
    7. Tab1_Grid.ItemsSource = Lesen_View


    Nur mal als Info, falls jemand anderes das hier mal liest und braucht.

    eichseinet schrieb:

    Also kann diese doch nicht als readonly angelegt werden.
    Doch. Im Konstruktor der Klasse können Readony-Variablen belegt werden.
    docs.microsoft.com/de-de/dotne…erence/modifiers/readonly
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    VB-Fragen über PN / Konversation werden ignoriert!

    eichseinet schrieb:

    Liest man eine Collection über einen Serializer ein, dann wird der Property offensichtlich eine neue Instanz der Collection zugewiesen.
    Hmm - dass hier was mit Serialisierung passieren soll - kann das sein, dass das eine neue Info ist, oder hab ich was übersehen? :S

    Wie dem auch sei. Dann würde ich eben das Deserialisat nicht zuweisen an die Property, sondern mittels .Add() die Elemente überspielen.

    VB.NET-Quellcode

    1. Seriendings.Deserialize(Leser).ForEach(AddressOf DC.Lesen_Coll.Add)
    Vielleicht auch nicht.
    Evtl. reagieren Bindings performanter, wenn man die ganze Collection austauscht, und das mittels INotifyPropertyChanged vermeldet.
    @ErfinderDesRades: Nein Du hast nix übersehen. Ist ein Testprojekt, das Stück für Stück um alle Funktionen erweitert wird, die im richtigen Programm dann nötig sind. Kurz nach meinem letzten Beitrag kam dann die Lese- und Schreibfunktion dazu.
    Ich lass den Schreibschutz jetzt vermutlich einfach mal weg. Finde das auch nicht schlimm. Es handelt sich um ein privates Projekt, an dem halt nur ich arbeite. Aber das letzte Wort ist da noch nicht gesprochen. Es wird halt noch vieles probiert. (Testprojekt halt)

    schönen Dank nochmal euch beiden