VB.NET-Tipp 0116: Attributgesteuerte Ressourcenbereinigung (Disposing)
von Spatzenkanonier
Beschreibung
Das Erstellen eines Objektes bedeutet im weitesten Sinne eine Veränderungen am Gesamtsystem, die nach Ablauf des Lebenszyklus des Objektes wieder rückgängig zu machen ist. Die üblichen Veränderungen (wie das Belegen von Speicherplatz) werden vom Garbage-Collector "aufgeräumt". Sehr viele Klassen nehmen aber weitergehende Veränderungen vor, welche der Garbage Collector nicht rückgängig zu machen weiß. Solche Klassen müssen eine Extra-Methode bereitstellen, Dispose(), in der sie für sich selbst aufräumen - sie implementieren die Schnittstelle "IDisposable". Dabei entsteht die Schwierigkeit, am Ende des Lebenszyklus eines komplexen Objektes alle seine Unterobjekte (Felder) zuverlässig disposen zu müssen.
Die hier implementierte attributgesteuerte Ressourcen-Freisetzung ermöglicht dem Programmierer Klassenvariablen schon bei der Deklaration als Disposable zu kennzeichnen, um die Ressourcen-Freigabe kümmert sich dann die Basisklasse.
Das Beispiel malt Kreise auf ein Control - beim OwnerDrawing finden besonders viele auf diese Weise ressourcenbelegende Objekte Verwendung. Ein hilfreicher Artikel auf CodeProject zu diesem Thema ist Implementing the Dispose Pattern properly.
Schwierigkeitsgrad: | Framework-Version(en): .NET Framework 2.0, .NET Framework 3.0, .NET Framework 3.5 | .NET-Version(en): Visual Basic 2005, Visual Basic 2008 | Download: |
' Dieser Quellcode stammt von http://www.activevb.de ' und kann frei verwendet werden. Für eventuelle Schäden ' wird nicht gehaftet. ' Um Fehler oder Fragen zu klären, nutzen Sie bitte unser Forum. ' Ansonsten viel Spaß und Erfolg mit diesem Source! ' Projektversion: Visual Studio 2005 ' Option Strict: An ' ' Referenzen: ' - System ' - System.Data ' - System.Drawing ' - System.Windows.Forms ' - System.Xml ' ' Imports: ' - Microsoft.VisualBasic ' - Microsoft.VisualBasic.ControlChars ' - System ' - System.Collections ' - System.Collections.Generic ' - System.Data ' - System.Drawing ' - System.Diagnostics ' - System.Windows.Forms ' ' ############################################################################## ' ################################# Canvas.vb ################################## ' ############################################################################## ''' <summary> ''' Zeichenfläche, auf der DrawCircle-Objekte dargestellt werden ''' </summary> Public Class Canvas : Inherits Control <Disposable()> Private _DrawCircles As New DisposeList(Of DrawCircle) Protected Overrides Sub OnMouseClick(ByVal e As MouseEventArgs) ' Entfernt einen DrawCircle oder setzt einen neuen MyBase.OnMouseClick(e) Dim dc As DrawCircle For Each dc In _DrawCircles If dc.Contains(e.Location) Then ' Neuzeichnen an dc's Position anfordern, dc entfernen, ' Methode verlassen Me.Invalidate(dc.Bounds) _DrawCircles.Remove(dc) Return End If Next ' Neuen DrawCircle erzeugen, positionieren, _DrawCircles zufügen, ' Neuzeichnen anfordern dc = New DrawCircle(30, Color.Red, 8) dc.Location = e.Location _DrawCircles.Add(dc) Me.Invalidate(dc.Bounds) End Sub Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs) MyBase.OnPaint(e) e.Graphics.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias For Each dc As DrawCircle In _DrawCircles dc.Draw(e.Graphics) Next End Sub Protected Overrides Sub Dispose(ByVal disposing As Boolean) ' Da Canvas nicht von DisposableBase erben kann, muss ' DisposableAttribute.DisposeFields() explizit aufgerufen werden. ' Anstatt attributgesteuerten Disposens könnte man einfach ' _DrawObjects.Clear() aufrufen. Aber wenn weitere disposable Klassen- ' Member hinzukommen... If disposing Then DisposableAttribute.DisposeFields(Me) MyBase.Dispose(disposing) End Sub End Class ' ############################################################################## ' ########################## DisposableAttribute.vb ############################ ' ############################################################################## Imports System.Reflection Imports System.Collections.Generic <AttributeUsage(AttributeTargets.Field)> _ Public Class DisposableAttribute : Inherits Attribute Private Shared _MyType As Type = GetType(DisposableAttribute) Private Shared _AttributedFields As New Dictionary(Of Type, FieldInfo()) Public Shared Sub DisposeFields(ByVal itm As Object) DisposeFields(itm.GetType, itm) End Sub ''' <exception cref="System.InvalidCastException"> ''' Wenn eines der als Disposable attributierten Felder von tp gar nicht ''' IDisposable implementiert ''' </exception> ''' <param name="tp">Der Typ, dessen Felder überprüft werden</param> ''' <param name="itm">Eine Instanz dieses Typs</param> Public Shared Sub DisposeFields(ByVal tp As Type, ByVal itm As Object) Dim fieldInfos As FieldInfo() = Nothing If Not _AttributedFields.TryGetValue(tp, fieldInfos) Then ' Wenn der Typ tp noch unbekannt ist, einen Eintrag tp / fieldInfos ' von allen FieldInfos des Typs anlegen und die mit <Disposable> ' attributierten aufsammeln Dim flds As FieldInfo() = itm.GetType.GetFields( _ BindingFlags.Instance Or BindingFlags.GetField Or _ BindingFlags.NonPublic Or BindingFlags.Public) Dim i As Integer For i = 0 To flds.Length - 1 If Not flds(i).IsDefined(DisposableAttribute._MyType, False) _ Then Exit For Next For ii As Integer = i + 1 To flds.Length - 1 If flds(ii).IsDefined(DisposableAttribute._MyType, False) Then flds(i) = flds(ii) i += 1 End If Next ReDim fieldInfos(i - 1) Array.Copy(flds, fieldInfos, i) _AttributedFields.Add(tp, fieldInfos) End If ' Alle Werte der fieldInfos disposen For Each fld As FieldInfo In fieldInfos Dim fieldValue As Object = fld.GetValue(itm) If fieldValue IsNot Nothing Then Debug.WriteLine("disposing " & fld.Name) DirectCast(fieldValue, IDisposable).Dispose() End If Next End Sub End Class ' ############################################################################## ' ############################# DisposableBase.vb ############################## ' ############################################################################## Imports System.ComponentModel Public Class DisposableBase : Implements IDisposable Private disposedValue As Boolean = False Private Shared _MyType As Type Protected Overridable Sub Dispose(ByVal disposing As Boolean) If _MyType Is Nothing Then _MyType = Me.GetType If Me.disposedValue Then Return If disposing Then DisposableAttribute.DisposeFields(_MyType, Me) Me.disposedValue = True End Sub Public Sub Dispose() Implements IDisposable.Dispose Dispose(True) GC.SuppressFinalize(Me) End Sub Protected Overrides Sub Finalize() Dispose(False) MyBase.Finalize() End Sub End Class ' ############################################################################## ' ############################## DisposeList.vb ################################ ' ############################################################################## ''' <summary> ''' Diese Auflistung gewährleistet, dass beim Entfernen eines Elementes dessen ''' Ressourcen freigegeben werden ''' </summary> Public NotInheritable Class DisposeList(Of T As IDisposable) Inherits System.Collections.ObjectModel.Collection(Of T) Implements IDisposable Protected Overrides Sub ClearItems() Implements IDisposable.Dispose For Each d As T In Me Debug.WriteLine("DisposeList disposes: " & d.GetType.Name) d.Dispose() Next MyBase.ClearItems() End Sub Protected Overrides Sub RemoveItem(ByVal index As Integer) Debug.WriteLine("DisposeList disposes: " & Me(index).GetType.Name) Me(index).Dispose() MyBase.RemoveItem(index) End Sub End Class ' ############################################################################## ' ############################### DrawCircle.vb ################################ ' ############################################################################## Imports System.Drawing Imports System.Drawing.Drawing2D Public Class DrawCircle : Inherits DisposableBase <Disposable()> Private _TemplatePath As New GraphicsPath <Disposable()> Private _DrawPath As New GraphicsPath <Disposable()> Private _Brush As SolidBrush <Disposable()> Private _Mtr As New Matrix Private _Location As Point Private _Bounds As Rectangle Public Sub New(ByVal radius As Single, ByVal color As Color, _ ByVal penWidth As Single) _Brush = New SolidBrush(color) _TemplatePath.AddEllipse(-radius, -radius, 2 * radius, 2 * radius) End Sub Public Sub Draw(ByVal g As Graphics) g.FillPath(_Brush, _DrawPath) End Sub Public ReadOnly Property Contains(ByVal pt As Point) As Boolean Get Return _DrawPath.IsVisible(pt) End Get End Property Public Property Location() As Point Get Return _Location End Get Set(ByVal value As Point) _Location = value _DrawPath.Reset() _DrawPath.AddPath(_TemplatePath, False) _Mtr.Reset() _Mtr.Translate(Location.X, Location.Y, MatrixOrder.Append) _DrawPath.Transform(_Mtr) _Bounds = Rectangle.Round(_DrawPath.GetBounds) ' Bei Zeichnen mit AntiAliasing können "Glättungspunkte" ausserhalb ' der eigentlichen Bounds liegen. Daher Bounds etwas größer fassen. _Bounds.Inflate(1, 1) End Set End Property ''' <summary> Das diese Figur umschließene Rechteck </summary> Public ReadOnly Property Bounds() As Rectangle Get Return _Bounds End Get End Property End Class
Ihre Meinung
Falls Sie Fragen zu diesem Artikel haben oder Ihre Erfahrung mit anderen Nutzern austauschen möchten, dann teilen Sie uns diese bitte in einem der unten vorhandenen Themen oder über einen neuen Beitrag mit. Hierzu können sie einfach einen Beitrag in einem zum Thema passenden Forum anlegen, welcher automatisch mit dieser Seite verknüpft wird.