Diffrence between Collection<T> and custom implement class - vb.net

Some people advice to inherit from Collection to get collection class. Some other people advice to have class and implement interfaces from scratch. I would like to understand when to use one over another.
I see that when i use:
class MyCollection
Inherits Collection(Of SomeObject)
I have possibility to add, insert, for each etc that's probably because Collection<T> uses internally List<T>
However if i just do:
class MyCollection : IList(Of SomeObject), IEnumerable<SomeObject>, IEnumerable(Of SomeObject)
myList As List(Of SomeObject)
i can also implement such things like Add(), Remove(), for each
Is it like it's good to use Collection(Of T) because it's already contains all of this implemented interfaces and inner List(Of T) rather that implementing all interfaces myself in self class from scratch? Is it the point people advice to inherit from COllection(Of T)?
EDIT (for further discussion):
Public Class Merge
Property Size As Integer
Property Datee As Date
Property Min As Integer
Property Max As Integer?
Property Value As Double
Public Sub New(min As Integer, max As Integer?, value As Integer)
Me.Min = min
Me.Max = max
Me.Value = value
End Sub
End Class
Public Enum SortCriteria
MinThenMax
MaxThenMin
End Enum
Public Class MergeComparer
Implements IComparer(Of Merge) 'do oddzielnej klasy sortowania obiektu jak tutaj potrzebujemy IComparer a nie IComparable (ten jest bezposrednio na klasie)
Public SortBy As SortCriteria = SortCriteria.MinThenMax
Public Function Compare(x As Merge, y As Merge) As Integer Implements IComparer(Of Merge).Compare
'to be implemented
End Function
End Class
Public Class MergeCollection
Inherits Collection(Of Merge)
Public SortBy As SortCriteria = SortCriteria.MinThenMax
''' <summary>
''' Ovveride because
''' There could be only one item on list which contains Max prop = Nothing
''' </summary>
''' <param name="index"></param>
''' <param name="item"></param>
Protected Overrides Sub InsertItem(index As Integer, item As Merge)
if IsNothing(item.Max)
If Items.Any(Function(myObject) IsNothing(Items.Max)) Then
Return
End If
End If
MyBase.InsertItem(index, item)
End Sub
Public Sub Sort()
Dim allItems = Items.ToArray()
Array.Sort(allItems)
For i = 0 To allItems.GetUpperBound(0)
Items(i) = allItems(i)
Next
End Sub
Public Sub Sort(comparison As Comparison(Of Merge))
Dim allItems = Items.ToArray()
Array.Sort(allItems, comparison)
For i = 0 To allItems.GetUpperBound(0)
Items(i) = allItems(i)
Next
End Sub
Public Sub Sort(comparer As IComparer(Of Merge))
Dim allItems = Items.ToArray()
Array.Sort(allItems, comparer)
For i = 0 To allItems.GetUpperBound(0)
Items(i) = allItems(i)
Next
End Sub
End Class

Here's an example of a custom collection that implements Collection(Of T) and then adds it's own Sort method:
Public Class StringCollection
Inherits Collection(Of String)
Public Sub Sort()
Dim allItems = Items.ToArray()
Array.Sort(allItems)
For i = 0 To allItems.GetUpperBound(0)
Items(i) = allItems(i)
Next
End Sub
End Class
Sample usage:
Dim strings As New StringCollection
strings.Add("Peter")
strings.Add("Paul")
strings.Add("Mary")
strings.Sort()
For Each s In strings
Console.WriteLine(s)
Next
That Sort method relies on the IComparable implementation of the items themselves. If you want to support other sorting methods or your items don't implement IComparable then you can implement different Sort methods, e.g.
Public Class StringCollection
Inherits Collection(Of String)
Public Sub Sort()
Dim allItems = Items.ToArray()
Array.Sort(allItems)
For i = 0 To allItems.GetUpperBound(0)
Items(i) = allItems(i)
Next
End Sub
Public Sub Sort(comparison As Comparison(Of String))
Dim allItems = Items.ToArray()
Array.Sort(allItems, comparison)
For i = 0 To allItems.GetUpperBound(0)
Items(i) = allItems(i)
Next
End Sub
End Class
Sample usage:
Dim strings As New StringCollection
strings.Add("Peter")
strings.Add("Paul")
strings.Add("Mary")
strings.Sort(Function(a, b) a.Length.CompareTo(b.Length))
For Each s In strings
Console.WriteLine(s)
Next
In that case, we're explicitly comparing the String items by their Length rather than their implicit "alphabetic" IComparable implementation.
You can include an overload of Sort for every relevant overload of Array.Sort and/or you can provide your own explicit comparisons if you want. Note that one of those overloads takes an IComparer. Here's how you might make use of that:
Public Class StringCollection
Inherits Collection(Of String)
Public Sub Sort()
Dim allItems = Items.ToArray()
Array.Sort(allItems)
For i = 0 To allItems.GetUpperBound(0)
Items(i) = allItems(i)
Next
End Sub
Public Sub Sort(comparison As Comparison(Of String))
Dim allItems = Items.ToArray()
Array.Sort(allItems, comparison)
For i = 0 To allItems.GetUpperBound(0)
Items(i) = allItems(i)
Next
End Sub
Public Sub Sort(comparer As IComparer(Of String))
Dim allItems = Items.ToArray()
Array.Sort(allItems, comparer)
For i = 0 To allItems.GetUpperBound(0)
Items(i) = allItems(i)
Next
End Sub
End Class
Here's an example comparer:
Public Class StringLengthComparer
Implements IComparer, IComparer(Of String)
Public Function Compare(x As Object, y As Object) As Integer Implements IComparer.Compare
Return Compare(CStr(x), CStr(y))
End Function
Public Function Compare(x As String, y As String) As Integer Implements IComparer(Of String).Compare
Return x.Length.CompareTo(y.Length)
End Function
End Class
Sample usage:
Dim strings As New StringCollection
strings.Add("Peter")
strings.Add("Paul")
strings.Add("Mary")
Dim comparer As New StringLengthComparer
strings.Sort(comparer)
For Each s In strings
Console.WriteLine(s)
Next
We're sorting the items by their Length again but, this time, we're using an object that implements IComparer to make the comparisons rather than a Comparison(Of T) delegate. You should notice, though, that the Compare method of the IComparer in this last example takes pretty much the exact same form as the Lambda used to create the Comparison(Of T) delegate in the previous example.

Related

System.ArgumentException: At least one object must implement IComparable

In my MergeCollection class i have overide InsertItem method to check for specific case. Nevertheless when it comes to the line Items.Any(.. it throws me exception as below.
System.ArgumentException: 'At least one object must implement IComparable.'
Merge class:
Public Class Merge
Property Min As Integer
Property Max As Integer?
Property Value As Double
Public Sub New(min As Integer, max As Integer?, value As Integer)
Me.Min = min
Me.Max = max
Me.Value = value
End Sub
End Class
Public Enum SortCriteria
MinThenMax
MaxThenMin
End Enum
Some comparer:
Public Class MergeComparer
Implements IComparer(Of Merge)
Public SortBy As SortCriteria = SortCriteria.MinThenMax
Public Function Compare(x As Merge, y As Merge) As Integer Implements IComparer(Of Merge).Compare
'to be implemented
End Function
End Class
Collection class:
Public Class MergeCollection
Inherits Collection(Of Merge)
Public SortBy As SortCriteria = SortCriteria.MinThenMax
Protected Overrides Sub InsertItem(index As Integer, item As Merge)
if IsNothing(item.Max)
If Items.Any(Function(myObject) IsNothing(Items.Max)) Then
Return
End If
End If
MyBase.InsertItem(index, item)
End Sub
Public Sub Sort()
Dim allItems = Items.ToArray()
Array.Sort(allItems)
For i = 0 To allItems.GetUpperBound(0)
Items(i) = allItems(i)
Next
End Sub
Public Sub Sort(comparison As Comparison(Of Merge))
Dim allItems = Items.ToArray()
Array.Sort(allItems, comparison)
For i = 0 To allItems.GetUpperBound(0)
Items(i) = allItems(i)
Next
End Sub
Public Sub Sort(comparer As IComparer(Of Merge))
Dim allItems = Items.ToArray()
Array.Sort(allItems, comparer)
For i = 0 To allItems.GetUpperBound(0)
Items(i) = allItems(i)
Next
End Sub
End Class
The code asks for Items.Max. To find the Max value, it has to compare the Items to each other.
Public Class Merge
Implements IComparable(Of Merge)
Property Min As Integer
Property Max As Integer?
Property Value As Double
Public Sub New()
' empty constructor
End Sub
Public Sub New(min As Integer, max As Integer?, value As Integer)
Me.Min = min
Me.Max = max
Me.Value = value
End Sub
Public Overloads Function CompareTo(other As Merge) As Integer Implements IComparable(Of Merge).CompareTo
If other Is Nothing Then Return 1
' Could use
' Return (Me.Value).CompareTo(other.Value)
' instead of the following code...
If Me.Value > other.Value Then Return 1
If Me.Value = other.Value Then Return 0
Return -1
End Function
End Class
I guess that you want a different criterion for the comparison.

Get elements with certain condition from List(Of)

I'm iterating through a List(Of MyClass) in order to find elements with certain conditions.
For example, in one case, I need to find all of these elements and do something with them:
For Each nCell As clsCell In colCell
If nCell.TempClickIndex = nCell.ClickIndex Then
If nCell.StandardCellType = eStandardCellType.SCT_SKYPEMESSAGE Then
I would like to know if there's any way to simplify this.
I'm dreaming of something like this:
For Each nCell As clsCell in colCell.GetSkypeCells()
The call "GetSkypeCells" would do just what I do above and would handle the selection internally.
Is there a way to do this?
Edit:
This is my colCell:
Public colCell As New clsCellListExtender.List(Of clsCell)
Imports System.Collections.ObjectModel
Public Class clsCellListExtender
Public Class List(Of T)
Inherits Collection(Of T)
Private _iID As Integer = 0
Private i As Integer = 0
Protected Overrides Sub InsertItem(index As Integer, item As T)
'your checks here
'i += 1
'If i > 20000 Then
' i = 0
'End If
Debug.Assert(g_bCheck = False)
If TypeOf (item) Is clsCell Then
_iID += 1
Dim nCell As clsCell = TryCast(item, clsCell)
nCell.TempID = _iID
End If
MyBase.InsertItem(index, item)
End Sub
End Class
End Class
You could use this:
For Each nCell as clsCell In colCell.Where(Function(x) x.TempClickIndex = x.ClickIndex AndAlso x.StandardCellType = eStandardCellType.SCT_SKYPEMESSAGE)
'Do stuff with nCell
Next
For your "dream" solution, you could add an extension method to whatever type colCell is that returns the result of the above LINQ.
Getting this to work with the nested class, and the generic type was a little tricky, but I finally got it.
Public Module Extensions
<System.Runtime.CompilerServices.Extension()> _
Public Function GetSkypeCells(Of T As clsCell)(colCell As clsCellListExtender.List(Of T)) As IEnumerable(Of T)
Return colCell.Where(Function(x) x.TempClickIndex = x.ClickIndex AndAlso x.StandardCellType = eStandardCellType.SCT_SKYPEMESSAGE)
End Function
End Module
Here is a small console application with a working extension method. I left the implementation blank to save space, but you should be able to fill it in from what is above. Just let me know if you have any issues.
Imports System.Collections.ObjectModel
Imports System.Runtime.CompilerServices
Module Module1
Sub Main()
Dim a As New clsCellListExtender.List(Of clsCell)
For Each cell As clsCell In a.GetSkypeCells()
'Do things with cell here
Next
End Sub
End Module
Public Class clsCellListExtender
Public Class List(Of T)
Inherits Collection(Of T)
Protected Overrides Sub InsertItem(index As Integer, item As T)
'...
End Sub
End Class
End Class
Public Class clsCell
'...
End Class
Module Extensions
<Extension>
Public Function GetSkypeCells(Of T As clsCell)(colCell As clsCellListExtender.List(Of T)) As IEnumerable(Of T)
Return colCell.Where(Function(x) x.TempClickIndex = x.ClickIndex AndAlso x.StandardCellType = eStandardCellType.SCT_SKYPEMESSAGE)
End Function
End Module
Try this:
For Each nCell As clsCell In colCell.FindAll(Function(c) c.TempClickIndex = c.ClickIndex And
c.StandardCellType = eStandardCellType.SCT_SKYPEMESSAGE)
Next
You can adapt this and create an extension-method, then you can call it with colCell.GetSkypeCells()
<Extension>
Public Function GetSkypeCells(c As List(Of clsCell)) As List(Of clsCell)
Return c.FindAll(Function(cc As clsCell) cc.TempClickIndex = cc.ClickIndex And
cc.StandardCellType = eStandardCellType.SCT_SKYPEMESSAGE)
End Function
You can use LINQ's extension method Where
Dim skypeCalls =
colCell.Where(Function(cell) cell.TempClickIndex = cell.ClickIndex).
.Where(Function(cell) cell.StandardCellType = eStandardCellType.SCT_SKYPEMESSAG)
For Each skypeCall in skypeCalls
' Do something
Next

Collection class of specific type containing basic features

Every time i use some class e.g Artikel as follows:
Public Class Artikel
Property ID As Integer
Property Nummer As String
Property Name As String
Property Position As Integer
End Class
For such classes i would like to have collection class. The features i would like to have is like:
--> Add (passing Artikel object)
--> Remove (passing Artikel object)
--> Sort entire collection (based on Position property desc/asc)
--> Compare two Artikels (pass by Artikels and tell by which property has to be compared)
--> Check whether two artikels equals
--> Every added artikel has to be marked by Key (so maybe dictionary)? <key><Artikel>
--> Remove Artikel (passing by Key index)
Could somone from you there tell me or even better provide example of collection class pass those requirments?
EDIT: Startup:
Artikel's collection:
Option Strict On
Public Class Articles
Public Property collection As Dictionary(Of Integer, Artikel)
Sub New()
'Initiate new collection
collection = New Dictionary(Of Integer, Artikel)
End Sub
'Add new Artikel to collection
Public Function AddToCollection(ByVal artikel As Artikel) As Boolean
collection.Add(artikel)
Return True
End Function
'Remove specific Artikel
Public Sub RemoveFromCollectionByArtikel(artikel As Artikel)
If Not IsNothing(collection) Then
collection.Remove(artikel)
End If
End Sub
'Get collection
Public Function GetCollection() As Dictionary(Of Integer, Artikel)
Return collection
End Function
'Sort collection by property position
Public Sub SortByPosition()
collection.Sort()
End Sub
'Remove specific sending keys and then reorder them
Public Sub RemoveAllMarkedAsDeleted(keys As List(Of Integer))
'-- Check whther anything has been marked as deleted
If keys.Count > 0 Then
For Each row In keys
collection.Remove(row)
Next
ReorderKeys()
End If
'Reorder all Artikels in collection
Private Sub ReorderKeys()
Dim newCollection As New Dictionary(Of Integer, Artikel)
Dim index As Integer = 0
For Each collitem In collection
newCollection.Add(index, collitem.Value)
index += 1
Next
collection.Clear()
collection = newCollection
End Sub
End Class
Artikel class (additionally i implemented IComparable to be able to sort)
Option Strict On
Public Class Artikel
Implements IComparable(Of Artikel)
Property ID As Integer
Property Nummer As String
Property Name As String
Property Position As Integer
Public Function CompareTo(pother As Artikel) As Integer Implements IComparable(Of Artikel).CompareTo 'we can sort because of this
Return String.Compare(Me.Position, pother.Position)
End Function
Public Shared Function FindPredicate(ByVal partikel As Artikel) As Predicate(Of Artikel)
Return Function(partikel2 As Artikel) partikel.ID = partikel2.ID
End Function
Public Shared Function FindPredicateByUserId(ByVal partikel As String) As Predicate(Of Artikel)
Return Function(partikel2 As Artikel) partikel = partikel2.ID
End Function
End Class
Parts of it look good, but I would ultimately do it a bit differently. First, consider overloads on the item class to make them easier to create and default initialization:
Public Class Article
Property ID As Integer = -1
Property Key As String = ""
Property Name As String = ""
Property Position As Integer = -1
Property PubDate As DateTime = DateTime.Minimum
Public Sub New()
End Sub
' whatever minimum data a new item requires
Public Sub New(k As String, n As String)
Key = k
Name = n
End Sub
' full initialization:
Public Sub New(k As String, n As String, pos As Int32,
pubDt As DateTime)
...
End Sub
End Class
I added some properties for variety, and I suspect "Nummer" might be the "Key" mentioned in the OP, but whatever it is, I would add it to the Article class as that name, if it has some importance.
You might need a simple ctor for serialization (???). Some of these will find and use a Private parameterless constructor, but your code will be forced to use one of the overloads in order to provide some minimum level of data when a new one is created.
You probably do not need IComparable. That is typically for more complex comparisons, such as multiple or complex properties. An example is a carton or box:
If (width = Other.Width) AndAlso (height = Other.Height) Then
Return 0
ElseIf (width = Other.Height) AndAlso (height = Other.Width) Then
Return 0
End If
Plus more gyrations to work out which is "less" than the other. One reason you dont need it, is because If Art1.Postion > Art2.Postion is trivial. The other reason in your case, is because a Dictionary cannot be sorted.
Rather than a Dictionary, an internal List would work better for some of the things you describe but still allow you to have it act like a Dictionary to the extent you need it to. For this, I might build it using ICollection<T>:
Public Class ArticleCollection
Implements ICollection(Of Article)
Pressing Enter after that line will add all the required methods including:
Public Sub Add(item As Article) Implements ICollection(Of Article).Add
Public Sub Clear() Implements ICollection(Of Article).Clear
Public Function Contains(item As Article) As Boolean Implements ICollection(Of Article).Contains
Public ReadOnly Property Count As Integer Implements ICollection(Of Article).Count
Public Function Remove(item As Article) As Boolean Implements ICollection(Of Article).Remove
It remains completely up to you how these are implemented. It also doesn't rule out adding methods such as RemoveAt(int32) or RemoveByKey(string) depending on what you need/how it will be used. One of the benefits to ICollection(Of T) is that it includes IEnumerable which will allow use for each loops (once you write the Enumerator): For Each art In Articles
To emulate a dictionary to allow only one item with a specific property value:
Public Class ArticleCollection
Implements ICollection(Of Article)
Private mcol As List(Of Article)
...
Public Sub Add(item As Article) Implements ICollection(Of Article).Add
' check for existing key
If KeyExists(item.Key) = False Then
mcol.Add(item)
End If
End Sub
You can also overload them:
' overload to match Article ctor overload
Public Sub Add(key As String, name As String)
If KeyExists(key) = False Then
' let collection create the new item
' with the minimum required info
mcol.Add(New Article(key, name))
End If
End Sub
If you add an Item Property, you can index the collection ( Articles(3) ):
Property Item(ndx As Int32) As Article
Get
If ndx > 0 AndAlso ndx < mcol.Count Then
Return mcol(ndx)
Else
Return Nothing
End If
End Get
Set(value As Article)
If ndx > 0 AndAlso ndx < mcol.Count Then
mcol(ndx) = value
End If
End Set
End Property
' overload for item by key:
Public Property Item(key As String) As Article
An Add method and an Item Property will be important if the collection will display in the standard NET CollectionEditor.
There are several ways to implement sorting. The easiest is to use linq in the code which uses your collection:
Articles = New ArticleCollection
' add Article items
Dim ArticlesByDate = Articles.OrderBy(Function(s) s.PubDate).ToList()
Where PubDate is one of the Article properties I added. The other way to handle sorting is by the collection class returning a new collection (but it is so simple to do, there is little need for it):
Friend Function GetSortedList(bSortAsc As Boolean) As List(Of Article)
If bSortAsc Then
Return mcol.OrderBy(Function(q) q.PubDate).
ThenBy(Function(j) j.Position).ToList()
Else
Return mcol.OrderByDescending(Function(q) q.PubDate).
ThenByDescending(Function(j) j.Position).ToList()
End If
End Function
Whether it implements ICollection(Of T), inherits from ICollection(Of T) or does work off a Dictionary depends entirely on what this is, how it is used and whatever rules and restrictions there are (including if it will be serialized and how). These are not things we know.
MSDN has an article on Guidelines for Collections which is excellent.
Create your class
Public Class Artikel
Property ID As Integer
Property Nummer As String
Property Name As String
Property Position As Integer
sub new (_ID as integer, _Nummer as string, _Name as string, _Position as integer)
ID = _ID
Nummer = _Nummer
Name = _Name
Position = _Position
End Sub
End Class
Create another class which holds a private list and add sub routines to it
Public Class ArtikelList
Private _List as new list (of Artikel)
Public sub remove(Key as integer)
Dim obj as Artikel = nothing
for each x as Artikel in _List
if x.ID = Key then
obj = x
exit for
end if
Next
if not isnothing(obj) then
_List.remove(obj)
end if
End sub
Sub Add(obj as Artikel)
Dim alreadyDeclared as boolean = falsse
for each x as Artikel in _List
if x.ID = obj.id then
alreadyDeclared = true
exit for
end if
Next
if not AlreadyDeclared then
_List.add(obj)
Else
'Somehow inform the user of the duplication if need be.
end if
End sub
End Class
Then use your list class.
dim L as new ArtikelList
L.add(new Artikel(1280, "AFKforever!", "Prof.FluffyButton", 96))
L.remove(1280)
I only added one sub routine as an example. I hope it helps but feel free to ask for more example routines.
This can also be done by creating a class which inherits from the list class, exposing all list class functionality but by using this method you are forced to create every subroutine that will be used. This way you only use routines that you created exclusively for the purpose Artikel objects handling.
Check if two Artikels are equal
Public Class Artikel
Property ID As Integer
Property Nummer As String
Property Name As String
Property Position As Integer
sub new (_ID as integer, _Nummer as string, _Name as string, _Position as integer)
ID = _ID
Nummer = _Nummer
Name = _Name
Position = _Position
End Sub
End Class
Public Overrides Overloads Function Equals(obj As Object) As Boolean
If obj Is Nothing OrElse Not Me.GetType() Is obj.GetType() Then
Return False
else
dim _obj as artikel = obj
if Me.ID = _obj.ID then
Return true
else Return False
End If
End Function
End Class
Use it like:
If x.equals(y) then
'they have the same ID
end if

Create a List property which cannot be changed externally

I have a public class in my VB.NET project which has a List(Of String) property. This list needs to be modified by other classes within the project, but since the class may (at some time in the future) be exposed outside the project, I want it to be unmodifiable at that level. The modification of the existing property within the project will only be done by calling the list's methods (notably .Add, occasionally .Clear), not by a wholesale replacement of the property value with a new List (which is why I have it as a ReadOnly property).
I have come up with a way of doing it, but I'm not sure that it's exactly what you would call "elegant".
It's this:
Friend mlst_ParameterNames As List(Of String) = New List(Of String)
Public ReadOnly Property ParameterNames() As List(Of String)
Get
Return New List(Of String)(mlst_ParameterNames)
End Get
End Property
Now this just works fine and dandy. Any class in the project which accesses the mlst_ParameterNames field directly can modify it as needed, but any procedures which access it through the public property can bang away at modifying it to their heart's content, but will get nowhere since the property procedure is always returning a copy of the list, not the list itself.
But, of course, that carries overhead which is why I feel that it's just... well, viscerally "wrong" at some level, even though it works.
The parameters list will never be huge. At most it will only contain 50 items, but more commonly less than ten items, so I can't see this ever being a performance killer. However it has of course set me to thinking that someone, with far more VB.NET hours under their belt, may have a much neater and cleaner idea.
Anyone?
Instead of creating a new copy of the original list, you should use the AsReadOnly method to get a read-only version of the list, like this:
Friend mlst_ParameterNames As List(Of String) = New List(Of String)
Public ReadOnly Property ParameterNames() As ReadOnlyCollection(Of String)
Get
Return mlst_ParameterNames.AsReadOnly()
End Get
End Property
According to the MSDN:
This method is an O(1) operation.
Which means that the speed of the AsReadOnly method is the same, regardless of the size of the list.
In addition to the potential performance benefits, the read-only version of the list is automatically kept in sync with the original list, so if consuming code keeps a reference to it, its referenced list will still be up-to-date, even if items are later added to or removed from the list.
Also, the list is truly read-only. It does not have an Add or Clear method, so there will be less confusion for others using the object.
Alternatively, if all you need is for consumers to be able to iterate through the list, then you could just expose the property as IEnumerable(Of String) which is, inherently, a read-only interface:
Public ReadOnly Property ParameterNames() As IEnumerable(Of String)
Get
Return mlst_ParameterNames
End Get
End Property
However, that makes it only useful to access the list in a For Each loop. You couldn't, for instance, get the Count or access the items in the list by index.
As a side note, I would recommend adding a second Friend property rather than simply exposing the field, itself, as a Friend. For instance:
Private _parameterNames As New List(Of String)()
Public ReadOnly Property ParameterNames() As ReadOnlyCollection(Of String)
Get
Return _parameterNames.AsReadOnly()
End Get
End Property
Friend ReadOnly Property WritableParameterNames() As List(Of String)
Get
Return _parameterNames
End Get
End Property
What about providing a Locked property that you can set, each other property then checks this to see if it's been locked...
Private m_Locked As Boolean = False
Private mlst_ParameterNames As List(Of String) = New List(Of String)
Public Property ParameterNames() As List(Of String)
Get
Return New List(Of String)(mlst_ParameterNames)
End Get
Set(value As List(Of String))
If Not Locked Then
mlst_ParameterNames = value
Else
'Whatever action you like here...
End If
End Set
End Property
Public Property Locked() As Boolean
Get
Return m_Locked
End Get
Set(value As Boolean)
m_Locked = value
End Set
End Property
-- EDIT --
Just to add to this, then, here's a basic collection...
''' <summary>
''' Provides a convenient collection base for search fields.
''' </summary>
''' <remarks></remarks>
Public Class SearchFieldList
Implements ICollection(Of String)
#Region "Fields..."
Private _Items() As String
Private _Chunk As Int32 = 16
Private _Locked As Boolean = False
'I've added this in so you can decide if you want to fail on an attempted set or not...
Private _ExceptionOnSet As Boolean = False
Private ptr As Int32 = -1
Private cur As Int32 = -1
#End Region
#Region "Properties..."
Public Property Items(ByVal index As Int32) As String
Get
'Make sure we're within the index bounds...
If index < 0 OrElse index > ptr Then
Throw New IndexOutOfRangeException("Values between 0 and " & ptr & ".")
Else
Return _Items(index)
End If
End Get
Set(ByVal value As String)
'Make sure we're within the index bounds...
If index >= 0 AndAlso Not _Locked AndAlso index <= ptr Then
_Items(index) = value
ElseIf _ExceptionOnSet Then
Throw New IndexOutOfRangeException("Values between 0 and " & ptr & ". Use Add() or AddRange() method to append fields to the collection.")
End If
End Set
End Property
Friend Property ChunkSize() As Int32
Get
Return _Chunk
End Get
Set(ByVal value As Int32)
_Chunk = value
End Set
End Property
Public ReadOnly Property Count() As Integer Implements System.Collections.Generic.ICollection(Of String).Count
Get
Return ptr + 1
End Get
End Property
''' <summary>
''' Technically unnecessary, just kept to provide coverage for ICollection interface.
''' </summary>
''' <returns>Always returns false</returns>
''' <remarks></remarks>
Public ReadOnly Property IsReadOnly() As Boolean Implements System.Collections.Generic.ICollection(Of String).IsReadOnly
Get
Return False
End Get
End Property
#End Region
#Region "Methods..."
Public Shadows Sub Add(ByVal pItem As String) Implements System.Collections.Generic.ICollection(Of String).Add
If Not _Items Is Nothing AndAlso _Items.Contains(pItem) Then Throw New InvalidOperationException("Field already exists.")
ptr += 1
If Not _Items Is Nothing AndAlso ptr > _Items.GetUpperBound(0) Then SetSize()
_Items(ptr) = pItem
End Sub
Public Shadows Sub AddRange(ByVal collection As IEnumerable(Of String))
Dim cc As Int32 = collection.Count - 1
For sf As Int32 = 0 To cc
If _Items.Contains(collection.ElementAt(sf)) Then
Throw New InvalidOperationException("Field already exists [" & collection.ElementAt(sf) & "]")
Else
Add(collection.ElementAt(sf))
End If
Next
End Sub
Public Function Remove(ByVal item As String) As Boolean Implements System.Collections.Generic.ICollection(Of String).Remove
Dim ic As Int32 = Array.IndexOf(_Items, item)
For lc As Int32 = ic To ptr - 1
_Items(lc) = _Items(lc + 1)
Next lc
ptr -= 1
End Function
Public Sub Clear() Implements System.Collections.Generic.ICollection(Of String).Clear
ptr = -1
End Sub
Public Function Contains(ByVal item As String) As Boolean Implements System.Collections.Generic.ICollection(Of String).Contains
Return _Items.Contains(item)
End Function
Public Sub CopyTo(ByVal array() As String, ByVal arrayIndex As Integer) Implements System.Collections.Generic.ICollection(Of String).CopyTo
_Items.CopyTo(array, arrayIndex)
End Sub
#End Region
#Region "Private..."
Private Sub SetSize()
If ptr = -1 Then
ReDim _Items(_Chunk)
Else
ReDim Preserve _Items(_Items.GetUpperBound(0) + _Chunk)
End If
End Sub
Public Function GetEnumerator() As System.Collections.Generic.IEnumerator(Of String) Implements System.Collections.Generic.IEnumerable(Of String).GetEnumerator
Return New GenericEnumerator(Of String)(_Items, ptr)
End Function
Private Function GetEnumerator1() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
Return GetEnumerator()
End Function
#End Region
End Class
Friend Class GenericEnumerator(Of T)
Implements IEnumerator(Of T)
#Region "fields..."
Dim flist() As T
Dim ptr As Int32 = -1
Dim size As Int32 = -1
#End Region
#Region "Properties..."
Public ReadOnly Property Current() As T Implements System.Collections.Generic.IEnumerator(Of T).Current
Get
If ptr > -1 AndAlso ptr < size Then
Return flist(ptr)
Else
Throw New IndexOutOfRangeException("=" & ptr.ToString())
End If
End Get
End Property
Public ReadOnly Property Current1() As Object Implements System.Collections.IEnumerator.Current
Get
Return Current
End Get
End Property
#End Region
#Region "Constructors..."
Public Sub New(ByVal fieldList() As T, Optional ByVal top As Int32 = -1)
flist = fieldList
If top = -1 Then
size = fieldList.GetUpperBound(0)
ElseIf top > -1 Then
size = top
Else
Throw New ArgumentOutOfRangeException("Expected integer 0 or above.")
End If
End Sub
#End Region
#Region "Methods..."
Public Function MoveNext() As Boolean Implements System.Collections.IEnumerator.MoveNext
ptr += 1
Return ptr <= size
End Function
Public Sub Reset() Implements System.Collections.IEnumerator.Reset
ptr = -1
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
GC.SuppressFinalize(Me)
End Sub
#End Region
End Class

A class can only be a template for a single object not a template for a collection

I have a simple class List.vb which is the following:
Public Class List
Public fList As List(Of Integer)
Public Sub New()
fList = New List(Of Integer)
fList.Add(1)
fList.Add(2)
fList.Add(3)
fList.Add(4)
fList.Add(5)
End Sub
End Class
The Console application is using this class like the following:
Module Module1
Sub Main()
Dim fObject As List = New List
Dim cnt As Integer = 0
For Each x As Integer In fObject.fList
Console.WriteLine("hello; {0}", fObject.fList.Item(cnt).ToString())
cnt = cnt + 1
Next
Console.WriteLine("press [enter] to exit")
Console.Read()
End Sub
End Module
Can I change the class code so that List.vb is a list(of integer) type?
This would mean that in the Console code I could replace In fObject.fList with just In fObject?
Or am I barking up the wrong tree - should classes be single objects and lists should be collections of classes ?
Yes, you can do that. In order for an object to be compatible with For Each, it must have a GetEnumerator function:
Public Function GetEnumerator() As IEnumerator _
Implements IEnumerable.GetEnumerator
Return New IntListEnum(fList)
End Function
The IntListEnum class must, in turn, implement IEnumerator, like this:
Public Class IntListEnum Implements IEnumerator
Private listInt As List(Of Integer)
Dim position As Integer = -1
Public Sub New(ByVal fList As List(Of Integer))
listInt = fList
End Sub
Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext
position = position + 1
Return (position < listInt.Count)
End Function
Public Sub Reset() Implements IEnumerator.Reset
position = -1
End Sub
Public ReadOnly Property Current() As Object Implements IEnumerator.Current
Get
Try
Return listInt(position)
Catch ex As IndexOutOfRangeException
Throw New InvalidOperationException()
End Try
End Get
End Property
End Class
Now you can make fList private, and iterate your List as follows:
For Each x As Integer In fObject
You can see a complete example here.
The answer that dasblinkenlight has provided is excellent, but if all you need is a list that of integers that is pre-populated, you can just inherit from List(Of Integer) and then have the class populate itself in the constructor:
Public Class List
Inherits List(Of Integer)
Public Sub New()
Add(1)
Add(2)
Add(3)
Add(4)
Add(5)
End Sub
End Class
When you inherit from List(Of Integer), your class automatically gets all of the functionality implemented by that type, so your class also becomes a list class that works the same way. Then, you can just use it like this:
Dim fObject As New List()
For Each x As Integer In fObject
Console.WriteLine("hello; {0}", x)
Next