Die Community zu .NET und Classic VB.
Menü

VB.NET-Tipp 0116: Attributgesteuerte Ressourcenbereinigung (Disposing)

 von 

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:

Schwierigkeitsgrad 3

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:

Download des Beispielprojektes [15,04 KB]

' 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

    ' Die Grund-Idee zu einer solchen Klasse stammt von Robert Closheim

    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.