Public Class BlockClient

  Public Shared Sub Test()
    Dim Count As Integer = 1

    Do While Count > 0
      Console.Write("Number of blocks (0 to exit): ")
      Count = System.Convert.ToInt32(Console.ReadLine())

      If Count = 0 Then
        Exit Do
      End If

      Console.WriteLine("Creating ArrayList of {0:d} blocks {1:mm:ss}...", Count, DateTime.Now)
      Dim Blocks As New ArrayList(Count)

      Dim i As Integer

      Console.WriteLine("Creating {0:d} blocks {1:mm:ss}...", Count, DateTime.Now)
      For i = 0 To Count - 1
        Blocks.Add(New Block(String.Format("Block #{0:d}", i + 1)))
      Next

      Console.WriteLine("Adding {0:d} event handlers {1:mm:ss}...", Count * 2, DateTime.Now)
      For i = 0 To Count - 1
        AddHandler CType(Blocks(i), Block).OnCaptionChanging, AddressOf OnCaptionChanging
        AddHandler CType(Blocks(i), Block).OnCaptionChanged, AddressOf OnCaptionChanged
      Next

      Console.WriteLine("Changing block #{0:d} {1:mm:ss}...", Count \ 2, DateTime.Now)
      CType(Blocks(Count \ 2), Block).Caption = "New value"

      Console.WriteLine("Finished, press ENTER to GC.Collect()")
      Console.ReadLine()
      Blocks = Nothing
      GC.Collect()
      GC.WaitForPendingFinalizers()
    Loop
  End Sub

  Protected Shared Sub OnCaptionChanging(ByVal sender As Object, ByVal e As Block.CaptionEventArgs)
    Console.WriteLine("Changing: {0} -> {1}", e.OldCaption, e.NewCaption)
  End Sub


  Protected Shared Sub OnCaptionChanged(ByVal sender As Object, ByVal e As Block.CaptionEventArgs)
    Console.WriteLine("Changed: {0} -> {1}", e.OldCaption, e.NewCaption)
  End Sub

End Class


Public Class Block

  Private _caption As String
  Private _id As Integer

  Private Shared _maxId As Integer

  Public Sub New()
    Me.New(String.Empty)
  End Sub

  Public Sub New(ByVal caption As String)
    _caption = caption
    _maxId += 1
    _id = _maxId
  End Sub


  Public Class CaptionEventArgs
    Inherits EventArgs

    Private _oldCaption As String
    Private _newCaption As String
    Private _canceled As Boolean

    Public Sub New(ByVal oldCaption As String, ByVal newCaption As String)
      _oldCaption = oldCaption
      _newCaption = newCaption
    End Sub



    Public ReadOnly Property OldCaption() As String
      Get
        Return _oldCaption
      End Get
    End Property

    Public ReadOnly Property NewCaption() As String
      Get
        Return _newCaption
      End Get
    End Property


    Public Property Cancel() As Boolean
      Get
        Return _canceled
      End Get
      Set(ByVal Value As Boolean)
        _canceled = Value
      End Set
    End Property

  End Class


  Public Event OnCaptionChanging(ByVal sender As Object, ByVal e As Block.CaptionEventArgs)
  Public Event OnCaptionChanged(ByVal sender As Object, ByVal e As Block.CaptionEventArgs)

  Public Property Caption() As String
    Get
      Return _caption
    End Get
    Set(ByVal Value As String)
      Dim Args As New Block.CaptionEventArgs(_caption, Value)
      RaiseEvent OnCaptionChanging(Me, Args)
      If Not Args.Cancel Then
        _caption = Value
        RaiseEvent OnCaptionChanged(Me, Args)
      End If
    End Set
  End Property
End Class