locked
Just not understanding collection in VB.net RRS feed

  • Question

  • I have a program I am converting from scratch to vb.net but I am just not grasping how collections should be written.

    I have a class called PacketSpot  this class defines what a packetspot consists of.. Source below:

    Public Class PacketSpot
        Private Callsign1 As String
        Private TString1 As String
        Private Freq1 As Double
        Private Comment1 As String
        Private QSXFreq1 As Double
        Private Spotter1 As String
        Private Band1 As String
        Private TS1 As String
        Private Count1 As Integer
        Public Sub New()
        End Sub
        Public Property Callsign() As String
            Get
                Return Callsign1
            End Get
            Set(ByVal Value As String)
                Callsign1 = Value
            End Set
        End Property
        Public Property TString() As String
            Get
                Return TString1
            End Get
            Set(ByVal Value As String)
                TString1 = Value
            End Set
        End Property
        Public Property Freq() As Double
            Get
                Return Freq1
            End Get
            Set(ByVal Value As Double)
                Freq1 = Value
            End Set
        End Property
        Public Property Comment() As String
            Get
                Return Comment1
            End Get
            Set(ByVal Value As String)
                Comment1 = Value
            End Set
        End Property
        Public Property QSXFreq() As Double
            Get
                Return QSXFreq1
            End Get
            Set(ByVal Value As Double)
                QSXFreq1 = Value
            End Set
        End Property
        Public Property Spotter() As String
            Get
                Return Spotter1
            End Get
            Set(ByVal Value As String)
                Spotter1 = Value
            End Set
        End Property
        Public Property Band() As String
            Get
                Return Band1
            End Get
            Set(ByVal Value As String)
                Band1 = Value
            End Set
        End Property
        Public Property TS() As String
            Get
                Return TString1
            End Get
            Set(ByVal Value As String)
                TString1 = Value
            End Set
        End Property
        Public Property Count() As Integer
            Get
                Return Count1
            End Get
            Set(ByVal Value As Integer)
                Count1 = Value
            End Set
        End Property
        Public Function IsIdenticalTo(ByVal ps As PacketSpot) As Boolean
            IsIdenticalTo = False
            If TS <> ps.TS Then Exit Function
            If Callsign <> ps.Callsign Then Exit Function
            If Freq <> ps.Freq Then Exit Function
            If QSXFreq <> ps.QSXFreq Then Exit Function
            IsIdenticalTo = True
        End Function
    End Class

    I also have a class to act as the manager for the collection of PacketSpots. Source Below:

    Option Explicit On
    Imports System.Collections
    Imports System.Collections.Specialized
    Public Class SpotManager
        Private mCol As Collection
        Public Sub New()
            Dim mCol As New Collection
        End Sub
        Public ReadOnly Property count() As Integer
            Get
                count = mCol.Count
            End Get
        End Property
        Public ReadOnly Property item(ByVal vntIndexKey As Object) As PacketSpot
            Get
                On Error Resume Next
                item = mCol(vntIndexKey)
            End Get
        End Property
        Public Function Add(ByVal member As PacketSpot, Optional ByVal Sort As Boolean = True) As Boolean
            Dim cnt As Integer
            cnt = count
            mCol.Add(member, member.Callsign & " " & member.Freq)
        End Function
    End Class

    I am not understanding what needs to be added to the above class so that in my main program I can use

            For Each ps In PacketManager
                If ps.Freq >= baseFreq Then xxx
                If ps.Freq <= MaxFreq Then xxx
            next
    to work with the collection. If someone could show me that I would be really appreciate it..


    Rick
    Friday, June 22, 2012 9:53 PM

Answers

  • I am trying to implement a collection of PacketSpot objects.

    In that case, the change should be:

        Private Callsign1 As String
        ...

        Public PropertyCallsign As String
           
    Get
               
    ReturnCallsign1

    and similarly for the others.

    To create a collection (List) of PacketSpot Objects use

      Dim PacketSpots as List(Of PacketSpot) = New List(Of PacketSpot)

    and use it as

      Dim thisPS as NewPacketSpot
      thisPS.CallSign = "WATG"
      'etc

      PacketSpots.Add(thisPS)

    The Manager is implemented as (assuming you choose to go with a disctionary and the callsign as the key - they are many other options):

    Option Explicit On
    Imports System.Collections
    Imports System.Collections.Specialized
    Public Class SpotManager
       
    Private mCol As Dictionary(Of String, PacketSpot)
       
    Public Sub New()
            mC
    ol = New Dictionary(Of String, PacketSpot)   
    End Sub
       
    Public ReadOnly Property Count As Integer
           
    Get
                Return
    mCol.Count
           
    End Get
       
    End Property
       
    Public ReadOnly Property Item(ByVal Key As String) As PacketSpot
           
    Get
                    Return mCol.Item(Key)
           
    End Get
       
    End Property
       
    Public Sub Add(ByVal Member As PacketSpot)
            mCol.Add(Member.CallSign, Member) 'Key, Object
        End Function
    End Class

    You might not need the manager at all.  If you use a collection instead of the manager you will probably need to implement whatever comparison you are doing that uses the IsIdentical method as a special function int eh applciation. You can avoid that, and use the collection methods (such as sort), if you implement IComparer or IComparable. See the discussion here:
    http://support.microsoft.com/kb/321292

    Like a lot of things in ,Net, if you do the work up front in the class (such as the comparison overloads) the application-level code becomes enormously simplified.

    Saturday, June 23, 2012 4:02 AM
  • Now I just need to figure out how to sort it by the ps.freq and I will be good to go..

    That's the reference I made to IComparer/IComparable.

    If you include the required comparison methods in your class and indicate the the class Implements those methods, then the Search and Sort methods of the collection are available to you.  But note that you can't sort a dictionary directly - you can sort the keys and then access the dictionary in key order.  In that case your sort could simply be a routine that gets all the keys from the dictionary into a list of string, sorts the list, and uses the sorted list for sequential access. This process might be used when sort functionality is required only in specific routines, and there could be different sort orders.

    The SortedDictionary or OrderedDictionary give you the benefits of a dictionary structure and ordering by key combined in one class.


    Saturday, June 23, 2012 10:22 PM
  • Hi Rick,

    There is typically more than one path to a solution. While there is nothing wrong with the path you've been set down, it does unfortunately bypass the answer to your original question: how do I implement my own custom collection?

    Looking at the code you were working with, I can see a couple of answers for the question "why not just use a List(Of T)"... first and foremost, you wanted a read-only Item property which could not throw an index out of range exception. This is something that the existing collection objects do not provide so a custom collection would be required. You also wanted an add method with optional sorting. While fairly trivial to implement, this is also not something inherently available on any existing collection.

    It may be that switching to a generic List(Of T) is ok in this particular scenario and you can forego the custom functionality, but there may also come a time when this is not acceptable and you really do want that read-only Item property or custom Add behavior. So here is an example of how you might go about implementing the original custom collection object.

    Public Class Form1
        Private _Manager As New SpotManager
     
        Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
            For Each ps As PacketSpot In _Manager
                If ps.Freq > 12345 Then
     
                End If
            Next
        End Sub
    End Class
     
    Public Class SpotManager
        Implements ICollection(Of PacketSpot)
     
        Private _InnerList As New List(Of PacketSpot)
     
        Public Sub Add(item As PacketSpot) Implements System.Collections.Generic.ICollection(Of PacketSpot).Add
            _InnerList.Add(item)
        End Sub
     
        Public Sub Add(item As PacketSpot, order As SortOrder)
            _InnerList.Add(item)
            Sort(order)
        End Sub
     
        Public Sub Clear() Implements System.Collections.Generic.ICollection(Of PacketSpot).Clear
            _InnerList.Clear()
        End Sub
     
        Public Function Contains(item As PacketSpot) As Boolean Implements System.Collections.Generic.ICollection(Of PacketSpot).Contains
            Return _InnerList.Contains(item)
        End Function
     
        Public Sub CopyTo(array() As PacketSpot, arrayIndex As Integer) Implements System.Collections.Generic.ICollection(Of PacketSpot).CopyTo
            _InnerList.CopyTo(array, arrayIndex)
        End Sub
     
        Public ReadOnly Property Count As Integer Implements System.Collections.Generic.ICollection(Of PacketSpot).Count
            Get
                Return _InnerList.Count
            End Get
        End Property
     
        Public Function GetEnumerator() As System.Collections.Generic.IEnumerator(Of PacketSpot) Implements System.Collections.Generic.IEnumerable(Of PacketSpot).GetEnumerator
            Return _InnerList.GetEnumerator
        End Function
     
        Protected Function GetEnumerator1() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
            Return GetEnumerator()
        End Function
     
        Public Function IndexOf(item As PacketSpot) As Integer
            Return _InnerList.IndexOf(item)
        End Function
     
        Public Function IndexOf(item As PacketSpot, index As Integer) As Integer
            Return _InnerList.IndexOf(item, index)
        End Function
     
        Public ReadOnly Property IsReadOnly As Boolean Implements System.Collections.Generic.ICollection(Of PacketSpot).IsReadOnly
            Get
                Return False
            End Get
        End Property
     
        Default Public ReadOnly Property Item(index As Integer) As PacketSpot
            Get
                If index > -1 AndAlso index < _InnerList.Count Then
                    Return _InnerList(index)
                End If
                Return Nothing
            End Get
        End Property
     
        Public Function Remove(item As PacketSpot) As Boolean Implements System.Collections.Generic.ICollection(Of PacketSpot).Remove
            Return _InnerList.Remove(item)
        End Function
     
        Public Sub RemoveAt(index As Integer)
            If index > -1 AndAlso index < _InnerList.Count Then
                _InnerList.RemoveAt(index)
            End If
        End Sub
     
        Public Sub Sort()
            Sort(SortOrder.Ascending)
        End Sub
     
        Public Sub Sort(order As SortOrder)
            If Not order = SortOrder.None Then
                _InnerList.Sort()
                If order = SortOrder.Descending Then
                    _InnerList.Reverse()
                End If
            End If
        End Sub
    End Class
     
    Public Class PacketSpot
        Implements IComparable
        Implements IComparable(Of PacketSpot)
        Implements IEquatable(Of PacketSpot)
     
        Private _Callsign As String
        Private _TString As String
        Private _Freq As Double
        Private _Comment As String
        Private _QSXFreq As Double
        Private _Spotter As String
        Private _Band As String
        Private _TS As String
        Private _Count As Integer
     
        'No need for empty Sub New as it is implicit
     
        Public Property Callsign() As String
            Get
                Return _Callsign
            End Get
            Set(ByVal Value As String)
                _Callsign = Value
            End Set
        End Property
     
        Public Property TString() As String
            Get
                Return _TString
            End Get
            Set(ByVal Value As String)
                _TString = Value
            End Set
        End Property
     
        Public Property Freq() As Double
            Get
                Return _Freq
            End Get
            Set(ByVal Value As Double)
                _Freq = Value
            End Set
        End Property
     
        Public Property Comment() As String
            Get
                Return _Comment
            End Get
            Set(ByVal Value As String)
                _Comment = Value
            End Set
        End Property
     
        Public Property QSXFreq() As Double
            Get
                Return _QSXFreq
            End Get
            Set(ByVal Value As Double)
                _QSXFreq = Value
            End Set
        End Property
     
        Public Property Spotter() As String
            Get
                Return _Spotter
            End Get
            Set(ByVal Value As String)
                _Spotter = Value
            End Set
        End Property
     
        Public Property Band() As String
            Get
                Return _Band
            End Get
            Set(ByVal Value As String)
                _Band = Value
            End Set
        End Property
     
        Public Property TS() As String
            Get
                Return _TString
            End Get
            Set(ByVal Value As String)
                _TString = Value
            End Set
        End Property
     
        Public Property Count() As Integer
            Get
                Return _Count
            End Get
            Set(ByVal Value As Integer)
                _Count = Value
            End Set
        End Property
     
        Public Function CompareTo(obj As Object) As Integer Implements System.IComparable.CompareTo
            If TypeOf obj Is PacketSpot Then
                Return CompareTo(DirectCast(obj, PacketSpot))
            End If
            Return -1
        End Function
     
        Public Function CompareTo(other As PacketSpot) As Integer Implements System.IComparable(Of PacketSpot).CompareTo
            Return Freq.CompareTo(other.Freq)
        End Function
     
        'IsIdenticalTo is a value-equality comparison method; so override Equals
        ' to perform a value comparison. The object will still contain a ReferenceEquals
        ' method to maintain reference equality comparisions.
        'In addition, we can implement iEquatable(of PacketSpot) to provide the
        ' stronly-typed equals method.
        Public Overrides Function Equals(obj As Object) As Boolean
            If TypeOf obj Is PacketSpot Then
                Return Equals(DirectCast(obj, PacketSpot))
            End If
            Return False
        End Function
     
        Public Overloads Function Equals(other As PacketSpot) As Boolean Implements System.IEquatable(Of PacketSpot).Equals
            Return TS = other.TS AndAlso Callsign = other.Callsign AndAlso Freq = other.Freq AndAlso QSXFreq = other.QSXFreq
        End Function
     
        'Custom object should return unique hash code for each distinct state. An easy solution is to ensure that the
        ' object has a distinct string representation based on its property values and then return a hash of that string.
        Public Overrides Function GetHashCode() As Integer
            Return ToString.GetHashCode
        End Function
     
        'Custom object can return a meaningful string representation; replace with appropriate string result
        Public Overrides Function ToString() As String
            Return String.Format("{0}, {1} {{2}/{3}}", TS, Callsign, Freq, QSXFreq)
        End Function
     
    End Class
     

    I made some minor changes to your variable names (following a fairly standardized convention for property value fields). Also note that "On Error Resume Next" is for backward compatibility only and should never be used in new projects. Exception handling is done with Try/Catch blocks and should not be used for validation whenever possible. In this case we can simply test the index to ensure it is within the bounds of the collection size.

    So we can create a custom collection by implementing IEnumerable, ICollection, or IList as appropriate. In this case we chose ICollection because we want all the methods of a standard collection but we need a custom Item property and IList would force us to have a read/write Item property. Note that we could alternately have our class inherit from an existing collection object and then override its properties and methods to add custom functionality (however, the properties and methods available to override will vary by collection type with some having more options than others), but for complete control over a custom collection we need to implement a collection-based interface.

    While you may be far enough into a working solution based on a generic list that it is not worth rewriting with a custom list, hopefully this will still provide you with some direction for tackling future collection problems.

     


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Monday, June 25, 2012 3:38 PM
    Moderator
  • Hi Eric,

    here's a starting point about collections: http://msdn.microsoft.com/en-us/library/7y3x785f.aspx

    Forget the old VB6 collection. There are Lists, Dictionaries, Queues and other types of collections now. If possible, don't use the Microsoft.VisualBasic namespace as a lot in it is just there for people used to it from VB6, but is just an indirection to the core Framework functionality.


    Armin

    Saturday, June 23, 2012 12:42 AM
  • I thought I wrote this already but I can't see it anywhere.

    The manager class is being replaced by the collection class of your choice.  If that collection class does not have all the functionality you require, there are two options.  One is to create special functions at the application code level to, like I described above for sorting the dictionary keys.  This may be suitable for very specialised functions that are not likely to be useful elsewhere and which depend more on that application code than on any particular feature of the objects. 

    Another, and usually preferable, option is to go back to your original concept of a manager, but implement it the .Net way.  This means that the customer manager class inherits from the selected collecton class, such as the Dictionary.  This immediately gives the manager class all the features of that collection class, without any other code being written.

    Then, routines can be added to the manager class as required.  These could include custom sorting and searching routines. The result is a manager class that has most of its functionality provided by the base collection class, but with special requirements of this particular manager provided by code within that class.


    Sunday, June 24, 2012 2:04 AM

All replies

  • Try something like below.  Import also System.Collections.Generic.  Generic collections let you make a collection of a particular type (PacketSpot in your case), and you should familiarize yourself with them.

    Public Class SpotManager
      ' maybe a better name than mCol?
      ' mCol is a generic List which is a special type of collection
      Private mCol As New List(Of PacketSpot)
      Public Sub x()
        For Each ps As PacketSpot In mCol
          ' you can now access ps.whatever
        Next ps
      End Sub
    End Class

    Friday, June 22, 2012 10:37 PM
  • Is there a reason you don't use a List<Of T>?

    Option Strict On
    Option Explicit On
    Option Infer On
    
    Module Module1
    
        Class PacketSpot
            Public Sub New()
                Freq = 0
            End Sub
            Public Sub New(ByVal f As Double)
                Freq = f
            End Sub
    
            Public Property Freq As Double
        End Class
    
        Sub Main()
            Dim packetSpots As New List(Of PacketSpot) From {New PacketSpot(2.0), New PacketSpot(3.0)}
    
            For Each a In packetSpots
                Console.WriteLine(a.Freq)
            Next
        End Sub
    
    End Module
    


    Tom Shelton

    Friday, June 22, 2012 10:39 PM
  • How would I be able to use the For Each ps as PackSpot in Mcol from within my main program?? I have alot of routines in the main program that access the data in the collection by using that method..
    Saturday, June 23, 2012 12:10 AM
  • Tom..

     I have never heard of the List<of T> I feel I am a very good VB6 programmer but only have about 3 weeks of using vb.net under my belt.

    I'm curious tho how does the data get added to the list? Is it added the same way as a normal collection or what? I understand that calling the new creates a new packetspot but I do not see from your example how it is added. I have been googling about this but I have not found a good answer yet..

    Saturday, June 23, 2012 12:14 AM
  • Hi Eric,

    here's a starting point about collections: http://msdn.microsoft.com/en-us/library/7y3x785f.aspx

    Forget the old VB6 collection. There are Lists, Dictionaries, Queues and other types of collections now. If possible, don't use the Microsoft.VisualBasic namespace as a lot in it is just there for people used to it from VB6, but is just an indirection to the core Framework functionality.


    Armin

    Saturday, June 23, 2012 12:42 AM
  • I think you may have headed off in the wrong direction.

    Are you trying to implement a collection of PacketSpot objects?  Or are you trying to work with collections as properties?  I think it's the latter.

    In your class, this is inconsistent:

        Private Callsign1 As String
        ...

        Public Property Callsign() As String
           
    Get
               
    Return Callsign1
    It should be  

        Private Callsign1(10) As String
        ...

        Public Property Callsign() As String
           
    Get
               
    Return Callsign1

    Then you could do:

    Dim thisPS as PacketSpot = New PacketSpot
    thisPS.CallSign(0) = "WSAGX"
    thisPS.CallSign(1) = "WACTU"

    and so on.

    If you ever needed something other than 10, then this value can be passed to the constuctor, and the CallSign1 array re-dimmed to that value in the New sub.  You could also provide methods in your class to redim the array.  That's starting to become messy.   A List avoids that.

        Private Callsign1 As List(Of String) = New List(Of String)
        ...

        Public Property Callsign As List(Of String)
           
    Get
               
    Return Callsign1
                etc

    Then use it as:

      Dim thisPS as PacketSpot = New PacketSpot
      thisPS.CallSign.Add("WSAGX")
      thisPS.CallSign.Add("WACTU")

    and

     TextBox1.Text = thisPS.CallSign(x)

    Saturday, June 23, 2012 1:46 AM
  • I am trying to implement a collection of PacketSpot objects.

    In VB6 the way the application works is packetSpots come into the main program via a telnet connection. These spots are broken down and data added to the packetspot class. The Packetspot is then added to a collection of packet spots for easy minipluation in the Spotmanager class. The spots are then displayed in a window for the user by the choice of the user. I have only posted a small look at the spots manager as there are 44 other functions that are used for minipuulating the packspots within the collection that I have not added yet.

    The number of packetspots is variable depending on how the user has things configured where there may be as many as 2000+ items in the collection at any given time.

    Saturday, June 23, 2012 3:17 AM
  • I am trying to implement a collection of PacketSpot objects.

    In that case, the change should be:

        Private Callsign1 As String
        ...

        Public PropertyCallsign As String
           
    Get
               
    ReturnCallsign1

    and similarly for the others.

    To create a collection (List) of PacketSpot Objects use

      Dim PacketSpots as List(Of PacketSpot) = New List(Of PacketSpot)

    and use it as

      Dim thisPS as NewPacketSpot
      thisPS.CallSign = "WATG"
      'etc

      PacketSpots.Add(thisPS)

    The Manager is implemented as (assuming you choose to go with a disctionary and the callsign as the key - they are many other options):

    Option Explicit On
    Imports System.Collections
    Imports System.Collections.Specialized
    Public Class SpotManager
       
    Private mCol As Dictionary(Of String, PacketSpot)
       
    Public Sub New()
            mC
    ol = New Dictionary(Of String, PacketSpot)   
    End Sub
       
    Public ReadOnly Property Count As Integer
           
    Get
                Return
    mCol.Count
           
    End Get
       
    End Property
       
    Public ReadOnly Property Item(ByVal Key As String) As PacketSpot
           
    Get
                    Return mCol.Item(Key)
           
    End Get
       
    End Property
       
    Public Sub Add(ByVal Member As PacketSpot)
            mCol.Add(Member.CallSign, Member) 'Key, Object
        End Function
    End Class

    You might not need the manager at all.  If you use a collection instead of the manager you will probably need to implement whatever comparison you are doing that uses the IsIdentical method as a special function int eh applciation. You can avoid that, and use the collection methods (such as sort), if you implement IComparer or IComparable. See the discussion here:
    http://support.microsoft.com/kb/321292

    Like a lot of things in ,Net, if you do the work up front in the class (such as the comparison overloads) the application-level code becomes enormously simplified.

    Saturday, June 23, 2012 4:02 AM
  • You don't want to replace Collection with List(Of PacketSpot), you want to replace SpotManager with a List(Of PacketSpot).

    SpotManager has Item, Add and Count, and it's job is to look after a bunch of PacketSpots

    List(Of PacketSpot) has Item, Add and Count, and it's job is to look after a bunch of PacketSpots

    Get rid of SpotManager.

    I just wrote a much longer spiel, but hit backspace which took my browser back and deleted it all. arrgh.

    Forget about Collection. It is deprecated in all but name (I guess there are rare circumstances...). You can bad mouth it at parties, gala luncheons and meetups if you like.

    Saturday, June 23, 2012 6:15 AM
  • "How would I be able to use the For Each ps as PackSpot in Mcol from within my main program?? I have alot of routines in the main program that access the data in the collection by using that method."

    Make mCol public instead of private.  Declare Public MyMgr as new SpotManager in a place that is accessable throughout your program, eg in a Module or as a shared public in a Class.  Then, you can code like this:  For Each ps As PacketSpot In MyMgr.mCol ...

    Saturday, June 23, 2012 9:52 AM
  • mCol should not be public.  But in any case making it public only allows code like
        ForEach ps As PacketSpot In MyMgr.mCol

    For OP to to do code like
        For Each ps as PackSpot in myMgr

    the the property that exposes the mCol collection has be set as the default property.

    Saturday, June 23, 2012 10:27 AM
  • Acamar

    This is working for me.. Thank you for showing me how to do this.

    Now I just need to figure out how to sort it by the ps.freq and I will be good to go..

    • Edited by ericellison Saturday, June 23, 2012 4:28 PM
    Saturday, June 23, 2012 3:10 PM
  • Now I just need to figure out how to sort it by the ps.freq and I will be good to go..

    That's the reference I made to IComparer/IComparable.

    If you include the required comparison methods in your class and indicate the the class Implements those methods, then the Search and Sort methods of the collection are available to you.  But note that you can't sort a dictionary directly - you can sort the keys and then access the dictionary in key order.  In that case your sort could simply be a routine that gets all the keys from the dictionary into a list of string, sorts the list, and uses the sorted list for sequential access. This process might be used when sort functionality is required only in specific routines, and there could be different sort orders.

    The SortedDictionary or OrderedDictionary give you the benefits of a dictionary structure and ordering by key combined in one class.


    Saturday, June 23, 2012 10:22 PM
  • I thought I wrote this already but I can't see it anywhere.

    The manager class is being replaced by the collection class of your choice.  If that collection class does not have all the functionality you require, there are two options.  One is to create special functions at the application code level to, like I described above for sorting the dictionary keys.  This may be suitable for very specialised functions that are not likely to be useful elsewhere and which depend more on that application code than on any particular feature of the objects. 

    Another, and usually preferable, option is to go back to your original concept of a manager, but implement it the .Net way.  This means that the customer manager class inherits from the selected collecton class, such as the Dictionary.  This immediately gives the manager class all the features of that collection class, without any other code being written.

    Then, routines can be added to the manager class as required.  These could include custom sorting and searching routines. The result is a manager class that has most of its functionality provided by the base collection class, but with special requirements of this particular manager provided by code within that class.


    Sunday, June 24, 2012 2:04 AM
  • I found a sort function when I googled instead of implementing the IComparer/IComparable which is this:

    PacketSpots.Sort(Function(x, y) x.Freq.CompareTo(y.Freq))

    But it has a quirk where when there is 2 or more items on the same freq the first time the window refreshes it is sorted ascending and the next time the window refreshes it will be decending.

    So it looks like I will need to implement the IComparer/IComparable.

    But my only question is where should I place them. In my main program ,PacketSpot, or in the manager?

    TIA

    Monday, June 25, 2012 1:14 PM
  • Hi Rick,

    There is typically more than one path to a solution. While there is nothing wrong with the path you've been set down, it does unfortunately bypass the answer to your original question: how do I implement my own custom collection?

    Looking at the code you were working with, I can see a couple of answers for the question "why not just use a List(Of T)"... first and foremost, you wanted a read-only Item property which could not throw an index out of range exception. This is something that the existing collection objects do not provide so a custom collection would be required. You also wanted an add method with optional sorting. While fairly trivial to implement, this is also not something inherently available on any existing collection.

    It may be that switching to a generic List(Of T) is ok in this particular scenario and you can forego the custom functionality, but there may also come a time when this is not acceptable and you really do want that read-only Item property or custom Add behavior. So here is an example of how you might go about implementing the original custom collection object.

    Public Class Form1
        Private _Manager As New SpotManager
     
        Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
            For Each ps As PacketSpot In _Manager
                If ps.Freq > 12345 Then
     
                End If
            Next
        End Sub
    End Class
     
    Public Class SpotManager
        Implements ICollection(Of PacketSpot)
     
        Private _InnerList As New List(Of PacketSpot)
     
        Public Sub Add(item As PacketSpot) Implements System.Collections.Generic.ICollection(Of PacketSpot).Add
            _InnerList.Add(item)
        End Sub
     
        Public Sub Add(item As PacketSpot, order As SortOrder)
            _InnerList.Add(item)
            Sort(order)
        End Sub
     
        Public Sub Clear() Implements System.Collections.Generic.ICollection(Of PacketSpot).Clear
            _InnerList.Clear()
        End Sub
     
        Public Function Contains(item As PacketSpot) As Boolean Implements System.Collections.Generic.ICollection(Of PacketSpot).Contains
            Return _InnerList.Contains(item)
        End Function
     
        Public Sub CopyTo(array() As PacketSpot, arrayIndex As Integer) Implements System.Collections.Generic.ICollection(Of PacketSpot).CopyTo
            _InnerList.CopyTo(array, arrayIndex)
        End Sub
     
        Public ReadOnly Property Count As Integer Implements System.Collections.Generic.ICollection(Of PacketSpot).Count
            Get
                Return _InnerList.Count
            End Get
        End Property
     
        Public Function GetEnumerator() As System.Collections.Generic.IEnumerator(Of PacketSpot) Implements System.Collections.Generic.IEnumerable(Of PacketSpot).GetEnumerator
            Return _InnerList.GetEnumerator
        End Function
     
        Protected Function GetEnumerator1() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
            Return GetEnumerator()
        End Function
     
        Public Function IndexOf(item As PacketSpot) As Integer
            Return _InnerList.IndexOf(item)
        End Function
     
        Public Function IndexOf(item As PacketSpot, index As Integer) As Integer
            Return _InnerList.IndexOf(item, index)
        End Function
     
        Public ReadOnly Property IsReadOnly As Boolean Implements System.Collections.Generic.ICollection(Of PacketSpot).IsReadOnly
            Get
                Return False
            End Get
        End Property
     
        Default Public ReadOnly Property Item(index As Integer) As PacketSpot
            Get
                If index > -1 AndAlso index < _InnerList.Count Then
                    Return _InnerList(index)
                End If
                Return Nothing
            End Get
        End Property
     
        Public Function Remove(item As PacketSpot) As Boolean Implements System.Collections.Generic.ICollection(Of PacketSpot).Remove
            Return _InnerList.Remove(item)
        End Function
     
        Public Sub RemoveAt(index As Integer)
            If index > -1 AndAlso index < _InnerList.Count Then
                _InnerList.RemoveAt(index)
            End If
        End Sub
     
        Public Sub Sort()
            Sort(SortOrder.Ascending)
        End Sub
     
        Public Sub Sort(order As SortOrder)
            If Not order = SortOrder.None Then
                _InnerList.Sort()
                If order = SortOrder.Descending Then
                    _InnerList.Reverse()
                End If
            End If
        End Sub
    End Class
     
    Public Class PacketSpot
        Implements IComparable
        Implements IComparable(Of PacketSpot)
        Implements IEquatable(Of PacketSpot)
     
        Private _Callsign As String
        Private _TString As String
        Private _Freq As Double
        Private _Comment As String
        Private _QSXFreq As Double
        Private _Spotter As String
        Private _Band As String
        Private _TS As String
        Private _Count As Integer
     
        'No need for empty Sub New as it is implicit
     
        Public Property Callsign() As String
            Get
                Return _Callsign
            End Get
            Set(ByVal Value As String)
                _Callsign = Value
            End Set
        End Property
     
        Public Property TString() As String
            Get
                Return _TString
            End Get
            Set(ByVal Value As String)
                _TString = Value
            End Set
        End Property
     
        Public Property Freq() As Double
            Get
                Return _Freq
            End Get
            Set(ByVal Value As Double)
                _Freq = Value
            End Set
        End Property
     
        Public Property Comment() As String
            Get
                Return _Comment
            End Get
            Set(ByVal Value As String)
                _Comment = Value
            End Set
        End Property
     
        Public Property QSXFreq() As Double
            Get
                Return _QSXFreq
            End Get
            Set(ByVal Value As Double)
                _QSXFreq = Value
            End Set
        End Property
     
        Public Property Spotter() As String
            Get
                Return _Spotter
            End Get
            Set(ByVal Value As String)
                _Spotter = Value
            End Set
        End Property
     
        Public Property Band() As String
            Get
                Return _Band
            End Get
            Set(ByVal Value As String)
                _Band = Value
            End Set
        End Property
     
        Public Property TS() As String
            Get
                Return _TString
            End Get
            Set(ByVal Value As String)
                _TString = Value
            End Set
        End Property
     
        Public Property Count() As Integer
            Get
                Return _Count
            End Get
            Set(ByVal Value As Integer)
                _Count = Value
            End Set
        End Property
     
        Public Function CompareTo(obj As Object) As Integer Implements System.IComparable.CompareTo
            If TypeOf obj Is PacketSpot Then
                Return CompareTo(DirectCast(obj, PacketSpot))
            End If
            Return -1
        End Function
     
        Public Function CompareTo(other As PacketSpot) As Integer Implements System.IComparable(Of PacketSpot).CompareTo
            Return Freq.CompareTo(other.Freq)
        End Function
     
        'IsIdenticalTo is a value-equality comparison method; so override Equals
        ' to perform a value comparison. The object will still contain a ReferenceEquals
        ' method to maintain reference equality comparisions.
        'In addition, we can implement iEquatable(of PacketSpot) to provide the
        ' stronly-typed equals method.
        Public Overrides Function Equals(obj As Object) As Boolean
            If TypeOf obj Is PacketSpot Then
                Return Equals(DirectCast(obj, PacketSpot))
            End If
            Return False
        End Function
     
        Public Overloads Function Equals(other As PacketSpot) As Boolean Implements System.IEquatable(Of PacketSpot).Equals
            Return TS = other.TS AndAlso Callsign = other.Callsign AndAlso Freq = other.Freq AndAlso QSXFreq = other.QSXFreq
        End Function
     
        'Custom object should return unique hash code for each distinct state. An easy solution is to ensure that the
        ' object has a distinct string representation based on its property values and then return a hash of that string.
        Public Overrides Function GetHashCode() As Integer
            Return ToString.GetHashCode
        End Function
     
        'Custom object can return a meaningful string representation; replace with appropriate string result
        Public Overrides Function ToString() As String
            Return String.Format("{0}, {1} {{2}/{3}}", TS, Callsign, Freq, QSXFreq)
        End Function
     
    End Class
     

    I made some minor changes to your variable names (following a fairly standardized convention for property value fields). Also note that "On Error Resume Next" is for backward compatibility only and should never be used in new projects. Exception handling is done with Try/Catch blocks and should not be used for validation whenever possible. In this case we can simply test the index to ensure it is within the bounds of the collection size.

    So we can create a custom collection by implementing IEnumerable, ICollection, or IList as appropriate. In this case we chose ICollection because we want all the methods of a standard collection but we need a custom Item property and IList would force us to have a read/write Item property. Note that we could alternately have our class inherit from an existing collection object and then override its properties and methods to add custom functionality (however, the properties and methods available to override will vary by collection type with some having more options than others), but for complete control over a custom collection we need to implement a collection-based interface.

    While you may be far enough into a working solution based on a generic list that it is not worth rewriting with a custom list, hopefully this will still provide you with some direction for tackling future collection problems.

     


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Monday, June 25, 2012 3:38 PM
    Moderator
  • Reed..

     Thanks for the code.. I will take a better look at this when I am back home this coming weekend. I really appreciate the help that is provided here..

    Rick

    Tuesday, June 26, 2012 12:47 PM