I read some articles and blogs on Implementation if IDisposable and GC working set. However, I could not understand the core areas of differentiation like:
Following is code of my test class:
Imports System.ComponentModel
Namespace Classes
Public Class BaseClass
Implements INotifyPropertyChanged
Implements IDisposable
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Protected Friend Sub NotifyPropertyChanged(ByVal info As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
End Sub
#Region "IDisposable Support"
Private disposedValue As Boolean ' To detect redundant calls
Protected Overridable Sub Dispose(disposing As Boolean)
If Not Me.disposedValue Then
If disposing Then
' TODO: dispose managed state (managed objects).
End If
End If
Me.disposedValue = True
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
#End Region
End Class
Public Class GenreClass
Inherits BaseClass
#Region "Private Variables"
Private _GenreValue As String
Private _IconValue As String
Private _IsSelectedValue As Boolean
Private _IsExpandedValue As Boolean
#End Region
#Region "Property Variables"
Property Genre As String
Get
Return _GenreValue
End Get
Set(Value As String)
If Not _GenreValue = Value Then
_GenreValue = Value
NotifyPropertyChanged("Genre")
End If
End Set
End Property
Property Icon As String
Get
Return _IconValue
End Get
Set(Value As String)
If Not _IconValue = Value Then
_IconValue = Value
NotifyPropertyChanged("Icon")
End If
End Set
End Property
Property IsSelected As Boolean
Get
Return _IsSelectedValue
End Get
Set(Value As Boolean)
If Not _IsSelectedValue = Value Then
_IsSelectedValue = Value
NotifyPropertyChanged("IsSelected")
End If
End Set
End Property
Property IsExpanded As Boolean
Get
Return _IsExpandedValue
End Get
Set(Value As Boolean)
If Not _IsExpandedValue = Value Then
_IsExpandedValue = Value
NotifyPropertyChanged("IsExpanded")
End If
End Set
End Property
#End Region
Protected Overrides Sub Dispose(disposing As Boolean)
Genre = Nothing
MyBase.Dispose(disposing)
End Sub
Public Overrides Function ToString() As String
Return Genre
End Function
End Class
End Namespace
My Test scenarios are as follows:
Test1:
Dim list1 As New List(Of HODLib.Classes.GenreClass)
For i = 0 To 4
Using z As New HODLib.Classes.GenreClass
With z
.Genre = "asdasd"
.Icon = "asdasdasdasdasd"
.IsExpanded = True
.IsSelected = True
End With
list1.Add(z)
End Using
Next
For Each z In list1
MessageBox.Show(z.ToString)
Next
result of test1 is that GC is called immediately and I loose access to resources, I get null message.
Test2:
Dim list1 As New List(Of HODLib.Classes.GenreClass)
For i = 0 To 4
Dim z As New HODLib.Classes.GenreClass
With z
.Genre = "asdasd"
.Icon = "asdasdasdasdasd"
.IsExpanded = True
.IsSelected = True
End With
list1.Add(z)
Next
For Each z In list1
MessageBox.Show(z.ToString)
Next
Result is Dispose is never called, even for z in forloop, I dont understand this, why is z not disposed, is it waiting because the list has reference to its values?
Test3:
Dim list1 As New List(Of HODLib.Classes.GenreClass)
For i = 0 To 4
Dim z As New HODLib.Classes.GenreClass
With z
.Genre = "asdasd"
.Icon = "asdasdasdasdasd"
.IsExpanded = True
.IsSelected = True
End With
list1.Add(z)
z.Dispose()
Next
For Each z In list1
MessageBox.Show(z.ToString)
Next
Result: Test2 vs test3 is calling the dispose manually after adding to the list. Result is that I lost access to resource, I get null message. Why did this happen although I added the object to list before calling the dispose method.
Thank you.
Unfortunately, the Dispose implementation that the Visual Basic IDE auto-generates is wrong in 99.9% of all cases. You should only use it if your class has a Finalize() method or the base class has a protected Dispose(Boolean) method. Which is extremely rare, finalizers are implemented by .NET Framework classes. It is their job to wrap unmanaged resources that should be released early.
It becomes 100% wrong when you find out that you can't write any meaningful code in the Dispose() method. Like this case, your class has no fields of a type that is disposable. Setting a field to Nothing has no effect.
You are adding a reference to the instance z which you create. When that instance is destroyed, the reference is no longer pointing to anything. The Using construct automatically calls Dispose for you. You can make a new copy of the object z by adding a method to the class:
Public Function Clone() As GenreClass
Return DirectCast(Me.MemberwiseClone(), GenreClass)
End Function
and use list1.Add(z.Clone).
See Object.MemberwiseClone Method for more information and if you need to create a deep copy.
Related
I am still very new to data binding in Windows Forms with Visual Basic .NET, but trying to get familiar with it. I tried looking for information on this already, but to no avail.
I want to set up two-way binding between a DataGridView control and a list of objects (let's say they are of a made-up type called MyListElementClass), in a manner similar to what I saw in this answer to another question. Below is my implementation for MyListElementClass, in a file called MyListElementClass.vb:
Imports System.ComponentModel
Imports System.Runtime.CompilerServices
<Serializable>
Public NotInheritable Class MyListElementClass
Implements INotifyPropertyChanged
Implements IMyListElementClass
#Region "Fields"
Private _a As UShort
Private _b As Double
Private _c, _d, _e As Boolean
' End fields region.
#End Region
#Region "INotifyPropertyChanged implementation"
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private Sub NotifyPropertyChanged(<CallerMemberName()> Optional ByVal propertyName As String = Nothing)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
' End INotifyPropertyChanged implementation region.
#End Region
#Region "IMyListElementClass implementation"
Public Property PropertyA As UShort Implements IMyListElementClass.PropertyA
Get
Return _a
End Get
Set(value As UShort)
If _a <> value Then
_a = value
NotifyPropertyChanged()
End If
End Set
End Property
Public Property PropertyB As Double Implements IMyListElementClass.PropertyB
Get
Return _b
End Get
Set(value As Double)
If _b <> value Then
_b = value
NotifyPropertyChanged()
End If
End Set
End Property
Public Property PropertyC As Boolean Implements IMyListElementClass.PropertyC
Get
Return _c
End Get
Set(value As Boolean)
If _c <> value Then
_c = value
NotifyPropertyChanged()
End If
End Set
End Property
Public Property PropertyD As Boolean Implements IMyListElementClass.PropertyD
Get
Return _d
End Get
Set(value As Boolean)
If _d <> value Then
_d = value
NotifyPropertyChanged()
End If
End Set
End Property
Public Property PropertyE As Boolean Implements IMyListElementClass.PropertyE
Get
Return _e
End Get
Set(value As Boolean)
If _e <> value Then
_e = value
NotifyPropertyChanged()
End If
End Set
End Property
' End IMyListElementClass implementation region.
#End Region
#Region "Constructors"
Public Sub New()
PropertyA = 0
PropertyB = 0
PropertyC = False
PropertyD = False
PropertyE = False
End Sub
Public Sub New(a As UShort, b As Double, c As Boolean, d As Boolean, e As Boolean)
Me.PropertyA = a
Me.PropertyB = b
Me.PropertyC = c
Me.PropertyD = d
Me.PropertyE = e
End Sub
Public Sub New(other As IMyListElementClass)
If other Is Nothing Then Throw New ArgumentNullException(NameOf(other))
CopyFrom(other)
End Sub
' End constructors region.
#End Region
#Region "Methods"
Public Sub CopyFrom(other As IMyListElementClass)
If other Is Nothing Then Throw New ArgumentNullException(NameOf(other))
With other
PropertyA = .PropertyA
PropertyB = .PropertyB
PropertyC = .PropertyC
PropertyD = .PropertyD
PropertyE = .PropertyE
End With
End Sub
' End methods region.
#End Region
End Class
The idea here is that the DataGridView control will show a list of available "slots" (rows) that instances of MyListElementClass can be entered into. However, some of these slots could be empty, and may need to be filled in or cleared later. The number of rows in the table is specified by a number entered elsewhere, so the user cannot add or remove rows on the fly; They have to work with the space that's given.
My current attempt at this is to have the DataGridView control bound to a BindingList(Of MyListElementClass), where its size is always equal to the number of available slots and empty slots are represented by null elements. However, I found that if I have these null values present in the BindingList(Of MyListElementClass), these rows cannot be edited by the user in the DataGridView control which is bound to it, and I'm not really sure how to handle this.
An example of what I'm trying to do in my user control which contains the DataGridView (named dgvDataGridView here and with columns already set up through the designer):
Public Class MyUserControl
Private _myBindingList As BindingList(Of MyListElementClass)
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
dgvDataGridView.AutoGenerateColumns = False ' Columns already created through the Visual Studio designer with the ordering and header text I want.
SetUpTableDataBinding()
End Sub
Private Sub SetUpTableDataBinding()
colA.DataPropertyName = NameOf(MyListElementClass.PropertyA)
colB.DataPropertyName = NameOf(MyListElementClass.PropertyB)
colC.DataPropertyName = NameOf(MyListElementClass.PropertyC)
colD.DataPropertyName = NameOf(MyListElementClass.PropertyD)
colE.DataPropertyName = NameOf(MyListElementClass.PropertyE)
Dim initialList As New List(Of MyListElementClass)(Enumerable.Repeat(Of MyListElementClass)(Nothing, 1)) ' First row will contain a null value, and hence be "empty".
_myBindingList = New BindingList(Of MyListElementClass)(initialList)
Dim source = New BindingSource(_myBindingList, Nothing)
dgvDataGridView.DataSource = source
' Some test data for data binding.
_myBindingList.AddNew() ' Adds a new MyListElementClass instance with default property values.
_myBindingList.Add(New MyListElementClass(2345, 7.4, False, True, False)) ' Just some sample values.
End Sub
End Class
After this user control loads, I can see an empty row, a row with default values for the MyListElementClass, and a row with some sample values appear, for three rows total. I can edit the second and third rows, but not the first (any values I enter immediately vanish).
Again, in completely unfamiliar territory here, so bear with me. If I cannot get this to work, then I will abandon this idea and return to manually setting and retrieving data in the DataGridView cells like I've always done up until now.
Null values cannot be edited, replace them with empty strings instead and I think you will find that it will work as intended.
I'm tying to create a generic solution for instantiating my forms using singleton behavior in vb.net. But it's not working anyway and always protecting me to compile:
Public Class SingletonGenerator(Of TForm)
Private _inst As Object
Public ReadOnly Property Instance As SingletonInstance(Of TForm)
Get
If _inst Is Nothing Then
_inst = New TForm()
End If
Return _inst
End Get
End Property
End Class
But this error restricts me to continue:
Error 9 'New' cannot be used on a type parameter that does not have a 'New' constraint.
And I'm not sure if I replace my code with New Form() it works as expected (because it create objects of parent form() class and may loose some initialization in child class.)
Can somebody please explain why this happen or how can I have singleton instances of objects in an OOP way which not require to copy/paste those common lines of code which are used in singleton on every new defined class?
You have to convince the compiler that the TForm type in fact has a parameterless constructor so that New TForm() can never fail. That requires a constraint.
Not the only thing you need to do, a Form object becomes unusable when it is closed. And you'll have to create another one to re-display it. Failure to do so causes an ObjectDisposedException at runtime. In other words, you should be interested in the Disposed event. That requires a further constraint, the TForm type parameter always needs to derive from Form. Required to convince the compiler that it is okay to use the event. Adding it up:
Public Class SingletonGenerator(Of TForm As {Form, New})
Private _inst As TForm
Public ReadOnly Property Instance As TForm
Get
If _inst Is Nothing Then
_inst = New TForm()
AddHandler _inst.Disposed, Sub() _inst = Nothing
End If
Return _inst
End Get
End Property
End Class
Do be a bit careful with this, you are painting yourself into a corner. You can only ever use this code to create form objects whose constructor takes no argument. In practice you may find they often need one.
Check this code:
Module Startup
Public Sub Main()
Dim f As Form = FormsManager.Instance.GetForm(Of Form1)()
f.ShowDialog()
Dim f1 As Form = FormsManager.Instance.GetForm(Of Form1)()
f1.ShowDialog()
End Sub
End Module
Public Class FormsManager
Private Shared _formsManager As FormsManager
Private _forms As List(Of Form)
Public Shared ReadOnly Property Instance As FormsManager
Get
If (_formsManager Is Nothing) Then
_formsManager = New FormsManager
End If
Return _formsManager
End Get
End Property
Private Sub New()
If _forms Is Nothing Then _forms = New List(Of Form)
End Sub
Public Function GetForm(Of T As {Form, New})() As Form
Dim f As Form = _forms.Where(Function(o) o.GetType = GetType(T)).SingleOrDefault
If f Is Nothing Then
f = New T
_forms.Add(f)
End If
Return f
End Function
End Class
This is what I finally produced (a generic singlton forms generator):
Imports System.Windows.Forms
Imports System.Runtime.CompilerServices
<HideModuleName()> _
Public Module SingletoneForms
<Extension> _
Public Function GetInstance(Of TForm As {Form, New})(ByRef obj As TForm) As TForm
Return SingletonForm(Of TForm).Instance
End Function
Public Class SingletonForm(Of TForm As {Form, New})
Private Shared WithEvents _inst As TForm
Public Shared Property Instance As TForm
Get
If _inst Is Nothing Then
SetInstance(New TForm())
End If
Return _inst
End Get
Set(value As TForm)
SetInstance(value)
End Set
End Property
Private Shared Sub SetInstance(ByVal newInst As TForm)
If _inst IsNot Nothing Then
RemoveHandler _inst.FormClosing, AddressOf FormClosing
End If
_inst = newInst
AddHandler _inst.FormClosing, AddressOf FormClosing
End Sub
Private Shared Sub FormClosing(sender As Object, e As FormClosingEventArgs)
If e.CloseReason = CloseReason.UserClosing Then
e.Cancel = True
_inst.Hide()
Else
_inst = Nothing
End If
End Sub
End Class
End Module
and call it simply this way:
frmMain.GetInstance().Show()
Form1.GetInstance().Show()
Form1.GetInstance().Hide()
Form2.GetInstance().ShowDialog()
I have a class in the code below, where besides equals and hash methods from IEqualityComparer which I use, I also want to implement add, remove, item, count from list and GetEnumerator (current,movenext,position). So I decided to use Inherits CollectionBase, IEnumerator and IEnumerable.
Anyway for instance when I use Add its not going to Add method in Part class, or when I do for each its not going to GetEnumerator move next. What is the problem?
This is the class:
Imports System.Collections.Generic
Public Class Part
Inherits CollectionBase
Implements IEqualityComparer(Of Part), IEnumerator(Of Part), IEnumerable(Of Part)
Private _values As List(Of Part)
Private _currentIndex As Integer
Public Property _comparisonType As EqualsComparmission
Public Sub New(ComparisonType As EqualsComparmission)
Me._comparisonType = ComparisonType
End Sub
Public Sub New(values As List(Of Part))
_values = New List(Of Part)(values)
Reset()
End Sub
Public Sub New()
End Sub
Public Property PartName() As String
Public Property PartId() As Integer
Public Overrides Function ToString() As String
Return "ID: " & PartId & " Name: " & PartName
End Function
Public Sub Add(ByVal value As Part)
Me.List.Add(value)
End Sub
Public Sub Remove(ByVal Index As Integer)
If Index >= 0 And Index < Count Then
List.Remove(Index)
End If
End Sub
Public ReadOnly Property Item(ByVal Index As Integer) As Part
Get
Return CType(List.Item(Index), Part)
End Get
End Property
Public ReadOnly Property Current() As Part
Get
Return _values(_currentIndex)
End Get
End Property
Public ReadOnly Property Current1 As Object Implements IEnumerator.Current
Get
Return Current
End Get
End Property
Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext
_currentIndex += 1
Return _currentIndex < _values.Count
End Function
Public Sub Reset() Implements IEnumerator.Reset
_currentIndex = -1
End Sub
#Region "IDisposable Support"
Private disposedValue As Boolean ' To detect redundant calls
' IDisposable
Protected Overridable Sub Dispose(disposing As Boolean)
If Not Me.disposedValue Then
If disposing Then
' TODO: dispose managed state (managed objects).
End If
' TODO: free unmanaged resources (unmanaged objects) and override Finalize() below.
' TODO: set large fields to null.
End If
Me.disposedValue = True
End Sub
' TODO: override Finalize() only if Dispose(ByVal disposing As Boolean) above has code to free unmanaged resources.
'Protected Overrides Sub Finalize()
' ' Do not change this code. Put cleanup code in Dispose(ByVal disposing As Boolean) above.
' Dispose(False)
' MyBase.Finalize()
'End Sub
' This code added by Visual Basic to correctly implement the disposable pattern.
Public Sub Dispose() Implements IDisposable.Dispose
' Do not change this code. Put cleanup code in Dispose(disposing As Boolean) above.
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
#End Region
Public Function GetEnumerator1() As IEnumerator(Of Part) Implements IEnumerable(Of Part).GetEnumerator
Return CType(Me, IEnumerator)
End Function
Public ReadOnly Property Current2 As Part Implements IEnumerator(Of Part).Current
Get
Return Current
End Get
End Property
Public Function Equals1(x As Part, y As Part) As Boolean Implements System.Collections.Generic.IEqualityComparer(Of Part).Equals
If x Is Nothing AndAlso y Is Nothing Then Return True
If x Is Nothing OrElse y Is Nothing Then Return False
Select Case _comparisonType
Case EqualsComparmission.PartId
Return x.PartId = y.PartId
Case EqualsComparmission.PartName
Return String.Equals(x.PartName, y.PartName)
Case EqualsComparmission.PartId_and_PartName
Return x.PartId = y.PartId AndAlso String.Equals(x.PartName, y.PartName)
Case Else
Throw New NotSupportedException("Unknown comparison type for parts: " & _comparisonType.ToString())
End Select
End Function
Public Function GetHashCode1(obj As Part) As Integer Implements System.Collections.Generic.IEqualityComparer(Of Part).GetHashCode
Select Case _comparisonType
Case EqualsComparmission.PartId
Return obj.PartId
Case EqualsComparmission.PartName
Return If(obj.PartName Is Nothing, 0, obj.PartName.GetHashCode())
Case EqualsComparmission.PartId_and_PartName
Dim hash = 17
hash = hash * 23 + obj.PartId
hash = hash * 23 + If(obj.PartName Is Nothing, 0, obj.PartName.GetHashCode())
Return hash
Case Else
Throw New NotSupportedException("Unknown comparison type for parts: " & _comparisonType.ToString())
End Select
End Function
End Class
and this is test code:
Dim parts As New List(Of Part)()
parts.Add(New Part() With { _
.PartName = "ala", _
.PartId = 11 _
})
parts.Add(New Part() With { _
.PartName = "shift lever", _
.PartId = 1634 _
})
For Each aPart As Part In parts
Console.WriteLine(aPart)
Next
Edited:
PartsCollection which implements ICollection for list operations:
Public Class PartsCollection
Implements ICollection(Of Part)
' Enumerators are positioned before the first element
' until the first MoveNext() call.
Dim position As Integer = -1
Private myList As List(Of Part)
Public Sub New()
If myList Is Nothing Then
myList = New List(Of Part)
End If
End Sub
Public Sub Add(item As Part) Implements ICollection(Of Part).Add
myList.Add(item)
End Sub
Public Sub Clear() Implements ICollection(Of Part).Clear
End Sub
Public Function Contains1(item As Part) As Boolean Implements ICollection(Of Part).Contains
Return myList.Contains(item)
End Function
Public Sub CopyTo(array() As Part, arrayIndex As Integer) Implements ICollection(Of Part).CopyTo
End Sub
Public Function GetEnumerator() As IEnumerator(Of Part) Implements IEnumerable(Of Part).GetEnumerator
Return New PartsEnumeration(myList)
End Function
Public ReadOnly Property Count As Integer Implements ICollection(Of Part).Count
Get
End Get
End Property
Public ReadOnly Property IsReadOnly As Boolean Implements ICollection(Of Part).IsReadOnly
Get
End Get
End Property
Public Function Remove(item As Part) As Boolean Implements ICollection(Of Part).Remove
End Function
Public Function GetEnumerator1() As IEnumerator Implements IEnumerable.GetEnumerator
End Function
End Class
PartsEnumeration for for each loop:
Public Class PartsEnumeration
Implements IEnumerator(Of Part)
Private mmyList As List(Of Part)
Dim position As Integer = -1
Public Sub New(ByVal myList As List(Of Part))
mmyList = myList
End Sub
Public ReadOnly Property Current As Part Implements IEnumerator(Of Part).Current
Get
Try
Return mmyList(position)
Catch ex As IndexOutOfRangeException
Throw New InvalidOperationException()
End Try
End Get
End Property
Public ReadOnly Property Current1 As Object Implements IEnumerator.Current
Get
Try
Return mmyList(position)
Catch ex As IndexOutOfRangeException
Throw New InvalidOperationException()
End Try
End Get
End Property
Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext
If position < mmyList.Count - 1 Then
position += 1
Return True
End If
Return False
End Function
Public Sub Reset() Implements IEnumerator.Reset
position = -1
End Sub
#Region "IDisposable Support"
Private disposedValue As Boolean ' To detect redundant calls
' IDisposable
Protected Overridable Sub Dispose(disposing As Boolean)
If Not Me.disposedValue Then
If disposing Then
' TODO: dispose managed state (managed objects).
End If
' TODO: free unmanaged resources (unmanaged objects) and override Finalize() below.
' TODO: set large fields to null.
End If
Me.disposedValue = True
End Sub
' TODO: override Finalize() only if Dispose(ByVal disposing As Boolean) above has code to free unmanaged resources.
'Protected Overrides Sub Finalize()
' ' Do not change this code. Put cleanup code in Dispose(ByVal disposing As Boolean) above.
' Dispose(False)
' MyBase.Finalize()
'End Sub
' This code added by Visual Basic to correctly implement the disposable pattern.
Public Sub Dispose() Implements IDisposable.Dispose
' Do not change this code. Put cleanup code in Dispose(disposing As Boolean) above.
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
#End Region
End Class
Test code:
Dim cos As New Part With { _
.PartName = "crank arm", _
.PartId = 1234 _
}
Dim cos3 As New Part With { _
.PartName = "cranddk arm", _
.PartId = 123334 _
}
myParts.Add(something1)
myParts.Add(something2)
Dim p As Boolean = myParts.Contains1(something1)
Console.WriteLine(p)
For Each ii In myParts
Console.WriteLine(ii.ToString)
Next
You are gluing 2 things together that are related but not the same thing and making a list of collections.
Dim parts As New List(Of Part)()
Since Part inherits from CollectionBase, you are making a List of Collections. A Collection class should be used to implement methods to manage the list/collection such as the Extension functionality in the other question. It would manage the List for you.
Class Part
Property Name As String
Property ID As Integer
End Class
Class Parts
Private myList As New List(Of Part)
Public Sub Add(item As Part)
Public Function IndexOfPartName...
etc
Public Sub Remove(item As Part)
' some might just be wrappers:
Public Function Contains(p As Part) As Boolean
Return myList.Contains(Part)
End Function
End Class
The code that consumes these:
Friend myParts As New Parts
myParts.Add(New Part(...))
Dim p As Part = myParts.IndexOfPartName("screws")
Once you work out the functionality you need and how it will be used, you might change it to Inherit Collection<T> so you can do For Each p As Part in Parts. But work on getting a viable collection class before tackling interfaces and such. Some of those may not be needed since Collection<T> implements all that for you.
You might be interested in: Guidelines for Collections
--
For your parts collection class to BE a collection rather then be a wrapper for an internal one:
Imports System.ComponentModel
Class Parts
Inherits Collection(Of Part)
' ALREADY implemeted for you are:
' Contains, Count, Add, Clear, IndexOf, Insert, EQUALS
' Item, Items, Remove, RemoveAt and RemoveItem
As such, all you need to do is override those methods which do something differently than you would like, or to extend functionality such as a IndexOfPartName.
ICollection requires you to write a collection from scratch, but the wheel has already been built.
I have a class that inherits from CollectionBase. I tried to use the contains method to detect whether the Key already exists before inserting a new one. Here is what I have tried.
<Serializable()> Public Class validationList
Inherits CollectionBase
Public Function Add(ByVal Item As validationItem) As Integer
Return Me.List.Add(Item)
End Function
Default Public ReadOnly Property Item(ByVal index As Integer) As validationItem
Get
Return CType(List.Item(index), validationItem)
End Get
End Property
Public Sub Remove(ByVal index As Integer)
Me.List.RemoveAt(index)
End Sub
Protected Overrides Sub OnInsert(ByVal index As Integer, ByVal value As Object)
If Me.List.Contains(value) Then MsgBox("Already exist")
MyBase.OnInsert(index, value)
End Sub
Public Function IndexOf(ByVal key As validationItem)
Return List.IndexOf(key)
End Function
Public Sub AddRange(ByVal item() As validationItem)
For counter As Integer = 0 To item.GetLength(0) - 1
List.Add(item(counter))
Next
End Sub
End Class
<Serializable()> Public Class validationItem
Implements IEquatable(Of validationItem)
Private _key As validationTypes
Private _value As String
Public Sub New()
' Empty constructor is needed for serialization
End Sub
Public Sub New(ByVal k As validationTypes, ByVal v As String)
_key = k
_value = v
End Sub
Public Enum validationTypes
Madatory = 0
[Integer] = 1
Numeric = 2
[Decimal] = 3
MaxValue = 4
MinValue = 5
MinLength = 6
Email = 7
End Enum
Public Property Value As String
Get
Return _value
End Get
Set(ByVal Value As String)
_value = Value
End Set
End Property
Public Property Key As validationTypes
Get
Return _key
End Get
Set(ByVal value As validationTypes)
_key = value
End Set
End Property
Protected Overloads Function Equals(ByVal eqItem As validationItem) As Boolean Implements IEquatable(Of Testing_Project.validationItem).Equals
If eqItem Is Nothing Then Return False
Return Me._key = eqItem.Key
End Function
Public Overrides Function Equals(ByVal eqItem As Object) As Boolean
If eqItem Is Nothing Then Return False
Dim eqItemObj As validationItem = TryCast(eqItem, validationItem)
If eqItemObj Is Nothing Then Return False
Return Equals(eqItemObj)
End Function
Public Overrides Function GetHashCode() As Integer
Return Me._key.GetHashCode()
End Function
End Class
The validationList will be exposed from a usercontrol as a property, so that items could be added from the designer. When adding items I need to detect whether they already exist. I tried overriding the OnInsert but this sometime return that duplicates exists even when their aren't and doesn't report that duplicate exist when I try to add existing keys.
This indirectly answers the question after dealing with the issue which emerged in comments about Collection(Of T):
Add a reference to System.Collections.ObjectModel if needed, then
Imports System.Collections.ObjectModel
' a ValidationItem collection class
Public Class ValidationItems
Inherits Collection(Of ValidationItem)
Public Shadows Sub Add(NewItem As ValidationItem)
' test for existence
' do not add if it is not unique
Dim dupe As Boolean = False
For n As Int32 = 0 To Items.Count - 1
If Items(n).Key = NewItem.Key Then
dupe = True
Exit For
End If
Next
If dupe = False then
items.Add(newitem)
End if
' I would actually use an IndexOfKey function which might
' be useful elsewhere and only add if the return is -1
If IndexOfKey(NewItem.Key) <> -1 Then
Items.Add(newItem)
End If
End Sub
Some NET collection types implement Add as a function and return the item added. This sounds weird since you pass it the item to add. But returning Nothing if the item cannot be added is a neat semaphore for "I cant/wont do that". I cant recall if the std NET Collection Editor recognizes that or not.
One problem with using Contains is that it will test if item passed as param is the same object as one in the list. They never will be the same object, even if they have the same values. Testing the key in a loop is simpler than calling a method which implements an interface. (That previous answer was totally valid in the context presented, but the context has changed).
Even if you stay with CollectionBase, you want to handle it in the Add. If you try to remove it in OnInsert, VS will have problems deserializing the collection.
Also, your validationitem needs a Name property or the Collection Editor will display "Myassembly+MyType" as the Name (or a ToString override).
Other issues:
I am not sure your IndexOf will work. The list contains ValidationItems (objects), but you check it for _key (string). This will not matter if you change to Collection(Of T) which implements it for you.
The simple ctor is needed by the Collection Editor, not serialization. But the important thing is that it is there.
As for the comment about all Zeroes coming back - that is because your ValidationItem is not yet decorated for designer serialization. Maybe not the Collection Property either, that isnt shown.
I'm creating a program in which I have a publicly defined boolean value
Public boolOverallStatus As Boolean = True
and I need to execute some code whenever the boolean value changes. In previous applications, an actual form item change handled this, but it can be changed by several different subs.
How would I handle this? I'm looking through msdn, but it's rather confusing.
In a nutshell: How to execute code when the event of a boolean value changing occurs.
Make it a property instead.
Private _boolOverallStatus As Boolean = True
Property boolOverallStatus As Boolean
Get
Return _boolOverallStatus
End Get
Set(ByVal value as Boolean)
If value <> _boolOverallStatus Then
_boolOverallStatus = value
'// handle more code changes here.'
End If
End Set
End Property
I use the following pattern which resembles what Microsoft do for most of their Changed events.
Class MyClass
Public Property OverallStatus As Boolean
Get
Return _OverallStatus
End Get
Set (value As Boolean)
If _OverallStatus = value Then Exit Property
_OverallStatus = value
OnOverallStatusChanged(EventArgs.Empty)
End Set
End Property
Private _OverallStatus As Boolean = False
Protected Overridable Sub OnOverallStatusChanged(e As EventArgs)
RaiseEvent OverallStatusChanged(Me, e)
End Sub
Public Event OverallStatusChanged As EventHandler
End Class
In VB, you can handle the event using the WithEvents and Handles keywords:
Class MyParent
Private WithEvents myObject As New MyClass()
Private Sub myobject_OverallStatusChanged(sender As Object, e As EventArgs) Handles myObject.OverallStatusChanged
' TODO: Write handler.
End Sub
End Class
The OnOverallStatusChanged function is useful for inheriting classes to get first shot at responding to the change.
Class MyOtherClass
Inherits MyClass
Protected Overrides Sub OnOverallStatusChanged(e As EventArgs)
' TODO: Do my stuff first.
MyBase.OnOverallStatusChanged(e)
End Sub
End Class
Use public properties instead of public variables. You can then put logic in the Set method of the property to execute whenever the property is.. well set.
http://msdn.microsoft.com/en-us/library/65zdfbdt%28v=VS.100%29.aspx
Private number As Integer = 0
Public Property MyNumber As Integer
' Retrieves number.
Get
Return number
End Get
' Assigns to number.
Set
CallLogicHere()
number = value
End Set
End Property
You could also define an event, which is fired each time the status changes. The advantage is, that changes can be handled by the parts of the application that depend on this status. Otherwise the logic would have to be implemented together with the status.
Other parts of the application can subscribe the event with AddHandler.
Public Class OverallStatusChangedEventArgs
Inherits EventArgs
Public Sub New(newStatus As Boolean)
Me.NewStatus = newStatus
End Sub
Private m_NewStatus As Boolean
Public Property NewStatus() As Boolean
Get
Return m_NewStatus
End Get
Private Set
m_NewStatus = Value
End Set
End Property
End Class
Module modGlobals
Public Event OverallStatusChanged As EventHandler(Of OverallStatusChangedEventArgs)
Private m_boolOverallStatus As Boolean = True
Public Property BoolOverallStatus() As Boolean
Get
Return m_boolOverallStatus
End Get
Set
If Value <> m_boolOverallStatus Then
m_boolOverallStatus = Value
OnOverallStatusChanged(Value)
End If
End Set
End Property
Private Sub OnOverallStatusChanged(newStatus As Boolean)
RaiseEvent OverallStatusChanged(GetType(modGlobals), New OverallStatusChangedEventArgs(newStatus))
End Sub
End Module