Fundamentals of the Collection Class

Introduction

Besides the System.Collections and the System.Collections.Generic namespaces, the .NET Framework provides additional collection classes in various other namespaces. One of those namespaces is System.Collections.ObjectModel. That namespace provides techniques of creating collections that emanate from other, existing, collections. For example, suppose you have a collection already but have a particular scenario in which you want to use that collection. Instead of creating a new collection, you can simply transfer the existing values to the new collection.

To state it another way, or to make it a little clearer, suppose you have a list of students whose records must be passed to a review. You can create another list and transfer the students records to that new list but block it, that is, make it read-only so that whoever accesses those records cannot change them. As another scenario, suppose you have a list of employees from one company that is merging with another company, you can easily transfer the existing employees records to a new list (for whatever reason) without creating a new collection. You can then modify the new collection from the existing records.

The System.Collections.ObjectModel namespace provides (two) abstract classes and other additional classes. The abstraction classes are named Collection and KeyedCollection.

Practical LearningPractical Learning: Introducing Geometric Accessories

  1. Start Microsoft Visual Studio
  2. Create a Windows Forms App named Exercise10

Using the Collection Class

The Collection<> class is one of the semi-complete classes of the .NET Framework. It starts as follows:

public class Collection<T> : IList<T>, 
    			     ICollection<T>,
    			     IEnumerable<T>,
    			     IList, ICollection,
    			     IEnumerable

As you can see, this is primarily a normal collection class that implements the IList<>, the ICollection<>, and the IEnumerable<> interfaces. This makes it possible to add items to a collection and to manage the list. Normally, you can use the Collection<> class "as is". It is equipped with all the regular properties and methods.

The Collection<> class is mostly useful if you want to create a class that impliments some particular behavior you want, in which case you would first create a class based on Collection<>. Here is an example:

using System.Collections.ObjectModel;

public class Series<T> : Collection<T>
{
}

Remember that the Collection<> class receives the ability to add a new item from the ICollection interface. Here are examples of calling it:

private void btnNumbers_Clicked(object sender, EventArgs e)
{
    numbers = new Series<int>();
    numbers.Add(2);
    numbers.Add(937);
    numbers.Add(49);
    numbers.Add(8);
    numbers.Add(64);
}

Remember that the Collection<> class inherits the Item and the Count properties. Here are examples of using them:

using System.Collections.ObjectModel;

namespace Numbers
{
    public partial class Exercise : Form
    {
        public Exercise()
        {
            InitializeComponent();
        }

        private void Exercise_Load(object sender, EventArgs e)
        {
            Series<int> numbers = new Series<int>();

            numbers.Add(2);
            numbers.Add(937);
            numbers.Add(49);
            numbers.Add(8);
            numbers.Add(64);

            for (int i = 0; i < numbers.Count; i++)
                lbxNumbers.Items.Add(numbers[i]);
        }
    }

    public class Series<T> : Collection<T>
    {
    }
}

Collection

As an inheritor of the ICollection<> interface, the Collection<> class has all the necessary primary functionalities. It is ready to let you:

Here are example of calling these methods (in the following examples, to make code easier, there was no error checking and exception handling was ignored):

using System.Collections.ObjectModel;

namespace Algebra
{
    public partial class Exercise : Form
    {
        Collection<int> numbers;

        public Exercise()
        {
            InitializeComponent();
        }

        private void Exercise_Load(object sender, EventArgs e)
        {
            numbers = new Collection<int>();
        }

        private void FillListBox()
        {
            lbxNumbers.Items.Clear();

            foreach(int nbr in numbers)
                lbxNumbers.Items.Add(nbr);
        }

        private void btnAdd_Click(object sender, EventArgs e)
        {
            numbers.Add(int.Parse(txtNumber.Text));

            txtNumber.Text = "";
            txtNumber.Focus();

            FillListBox();
        }

        private void btnInsert_Click(object sender, EventArgs e)
        {
            numbers.Insert(int.Parse(txtIndex.Text), int.Parse(txtNumber.Text));

            txtNumber.Text = "";
            txtIndex.Text = "";
            txtNumber.Focus();

            FillListBox();
        }

        private void btnCheckIndexOf_Click(object sender, EventArgs e)
        {
            if (numbers.Contains(int.Parse(txtIndexOf.Text)) )
                MessageBox.Show("The index of " + txtIndexOf.Text +
                                " is " + numbers.IndexOf(int.Parse(txtIndexOf.Text)),
                                "Algebra", MessageBoxButtons.OK,
                                MessageBoxIcon.Information);
            else
          MessageBox.Show("The collection doesn't contain " + txtIndexOf.Text + ". ",
                                "Algebra", MessageBoxButtons.OK,
                                MessageBoxIcon.Information);
        }

        private void btnRemove_Click(object sender, EventArgs e)
        {
            numbers.Remove(int.Parse(txtRemove.Text));

            FillListBox();
        }

        private void btnRemoveAllNumbers_Click(object sender, EventArgs e)
        {
            numbers.Clear();

            FillListBox();
        }

        private void btnClose_Click(object sender, EventArgs e)
        {
            Close();
        }
    }
}

Here is an example of running the program

Collection Collection
Collection Collection

Creating a Collection Class

Introduction

As stated already, the real role of the Collection<> class is to let you derive a class from it and implement some custom behaviors. To let you do this, the Collection<> class is equipped with a property and some methods that are not inherited from the ICollection<> interface. Because these are protected members, you must override them in your custom class.

You can start the class as follows:

using System.Collections.ObjectModel;

namespace Numbers
{    
    public class Series<T> : Collection<T>
    {
    	public Series() : base()
	{
	}
    }
}

Copying a Collection

The Collection<> class has two constructors. The default is used to create an empty collection. The other constructor uses the following syntax:

public Collection(IList<T> list);

This constructor allows you to create a list based on another existing list that is from a class that implements the IList<> interface. Here is an example of using this constructor:

using System.Collections.ObjectModel;

namespace Numerotation
{
    public partial class Exercise : Form
    {
        Series<int>? numbers;

        public Exercise()
        {
            InitializeComponent();
        }

        private void btnNumbers_Click(object sender, EventArgs e)
        {
            numbers = new Series<int>();
            numbers.Add(2);
            numbers.Add(937);
            numbers.Add(49);
            numbers.Add(8);
            numbers.Add(64);

            for (int i = 0; i < numbers.Count; i++)
                lbxNumbers.Items.Add(numbers[i]);
        }

        private void btnIntegers_Click(object sender, EventArgs e)
        {
            Collection<int> integers = new Collection<int>(numbers!);

            lbxNumbers.Items.Clear();

            for (int i = 0; i < integers.Count; i++)
                lbxNumbers.Items.Add(integers[i]);
        }
    }

    public class Series<T> : Collection<T>
    {
    }
}

Here is an example of running the program:

Collection Collection

On the other hand, if you had previously created a Collection<> object, to let you get it as an IList<>, the Collection<> class provides a property named Items that of type IList<>:

protected IList<T> Items { get; }

As you can see, Items is a protected property. This means that, to use it, you must first create a class that overrides it.

In both cases, whether using the Collection(IList<T> list) constructor or the Items property to get a new list, once you have the new collection, you can use it as you see fit. For example, you can add new values to it. Here are examples:

using System.Collections.ObjectModel;

namespace Numbers
{
    public partial class Exercise : Form
    {
        Series<int>? numbers;

        public Exercise()
        {
            InitializeComponent();
        }

        private void btnNumbers_Click(object sender, EventArgs e)
        {
            numbers = new Series<int>();
            numbers.Add(2);
            numbers.Add(937);
            numbers.Add(49);
            numbers.Add(8);
            numbers.Add(64);

            for (int i = 0; i < numbers.Count; i++)
                lbxNumbers.Items.Add(numbers[i]);
        }

        private void btnIntegers_Click(object sender, EventArgs e)
        {
            Series<int> integers = (Series<int>)numbers!.Items;

            integers.Add(1865);
            integers.Add(370);

            lbxNumbers.Items.Clear();

            for (int i = 0; i < integers.Count; i++)
                lbxNumbers.Items.Add(integers[i]);
        }
    }

    public class Series<T> : Collection<T>
    {
        public new IList<T> Items
        {
            get
            {
                return this;
            }
        }
    }
}

Here is an example of running the program:

Collection  Collection

Of course, you can also perform all the other allowed operations on the new collection.

Remember the Base

As mentiioned earlier, the Collection<> class provides some primary members you can use directly in your class. But because you must sometimes create your own class to customize Collection<>, to indicate that you will get the primary behaviors from Collection<>, you should call that base class on the constructor(s) and other members from your class. This is done by using the base keyword. Here are examples:

using System.Collections.ObjectModel;

namespace Numbers
{

    public class Series<T> : Collection<T>
    {
    	public Series() : base()
	{
	}

        public Series(IList<T> list) : base(list)
        {
        }

        public virtual new IList<T> Items
        {
            get
            {
                return this;
            }
        }
    }
}

Inserting an Item

One of the valuable operations you can perform on a list is to add a new item to it but include that new item somewhere inside the list. To let you customize the way an item should be inserted in a list, the Collection<> class provides a method named InsertItem. Its syntax is:

protected virtual void InsertItem(int index, T item);

This method takes two arguments: the item that will be inserted and the position it must occupy. Once again, the easisest implementation would consist of calling the parent's definition, which should be fine in a regular scenario. Here is an example:

using System.Collections.ObjectModel;

namespace Numbers
{
    public partial class Exercise : Form
    {
        Series<int>? numbers;

        public Exercise()
        {
            InitializeComponent();
        }

        private void btnNumbers_Click(object sender, EventArgs e)
        {
            numbers = new Series<int>();
            numbers.Add(2);
            numbers.Add(937);
            numbers.Add(49);
            numbers.Add(8);
            numbers.Add(64);

            for (int i = 0; i < numbers.Count; i++)
                lbxNumbers.Items.Add(numbers[i]);
        }

        private void btnIntegers_Click(object sender, EventArgs e)
        {
            numbers!.InsertItem(3, 10509);

            lbxNumbers.Items.Clear();

            for (int i = 0; i < numbers.Count; i++)
                lbxNumbers.Items.Add(numbers[i]);
        }
    }

    public class Series<T> : Collection<T>
    {
        public Series() : base()
        {

        }

        public Series(IList<T> list) : base(list)
        {

        }

        public new IList<T> Items
        {
            get
            {
                return this;
            }
        }

        public new void InsertItem(int index, T item)
        {
            base.InsertItem(index, item);
        }
    }
}

Here is an example of running the program:

Collection  Collection

An alternative is to create your own implementation. For example, if the user passes an invalid index, the compiler would throw an ArgumentOutOfRangeException exception. One probable solution is to indicate to the compiler what to do in case of a problem. Here is an example:

public new void InsertItem(int index, T item)
{
    if( (index is >= 0) && (index is <= Count) )
        base.InsertItem(index, item);
}

Inserting an Object

As another option to insert an item into a list, the Collection<> class is equipped with a method named Insert. Its syntax is:

public void Insert(index, T item);

This time, the method is public, which means it should be ready to be used. Otherwise, you can override it in your class, in which case you should (must) use the new operator when implementing it. Here is an example:

public class Series<T> : Collection<T>
{
    // . . .

    public new void Insert(int index, T item)
    {
        if (index < 0)
            throw new ArgumentOutOfRangeException("The value you provided " +
                                                  "for the index is not valid");
        else if( (index is >= 0) && (index is <= Count) )
            base.InsertItem(index, item);
        else
            // If the index is higher than Count, simply add the item (at the end of the list)
            this.Add(item);
    }

    public new void InsertItem(int index, T item)
    {
        base.InsertItem(index, item);
    }
}

Setting an Item

The Collection<> class implements the ICollection interface. This allows you to access an item using its index. Besides the ability to specify the value of an item based on its index, the Collection<> class provides a method named SetItem that makes it possible to change an item. Its syntax is:

protected virtual void SetItem(int index, T item);

The item argument holds the value that will be assigned to the item at the index position. When overriding this method, the simplest implementation consists of calling the same method of the parent class. Here is an example:

using System.Collections.ObjectModel;

namespace Numerotation
{
    public partial class Exercise : Form
    {
        Series<int>? numbers;

        public Exercise()
        {
            InitializeComponent();
        }

        private void btnNumbers_Click(object sender, EventArgs e)
        {
            numbers = new Series<int>();
            numbers.Add(2);
            numbers.Add(937);
            numbers.Add(49);
            numbers.Add(8);
            numbers.Add(64);

            for (int i = 0; i < numbers.Count; i++)
                lbxNumbers.Items.Add(numbers[i]);
        }

        private void btnIntegers_Click(object sender, EventArgs e)
        {
            numbers!.SetItem(2, 13579);

            lbxNumbers.Items.Clear();

            for (int i = 0; i < numbers.Count; i++)
                lbxNumbers.Items.Add(numbers[i]);
        }
    }

    public class Series<T> : Collection<T>
    {
        public new IList<T> Items
        {
            get
            {
                return this;
            }
        }

        public new void SetItem(int index, T item)
        {
            base.SetItem(index, item);
        }
    }
}

Here is an example of running the program:

Collection   Collection

Otherwise, when defining this method, you must decide what to do in case of this or that. For example, what should the compiler do if the index is negative? What if the passed index is higher than the total number of items in the collection? In this case, by default, the compiler would throw an IndexOutRangeException exception. One alternative is to ignore the new value if the index is out of range. Here is an example of implementing:

using System.Collections.ObjectModel;

namespace Numerotation
{
    public partial class Exercise : Form
    {
        Series<int>? numbers;

        public Exercise()
        {
            InitializeComponent();
        }

        private void btnNumbers_Click(object sender, EventArgs e)
        {
            numbers = new Series<int>();
            numbers.Add(2);
            numbers.Add(937);
            numbers.Add(49);
            numbers.Add(8);
            numbers.Add(64);

            for (int i = 0; i < numbers.Count; i++)
                lbxNumbers.Items.Add(numbers[i]);
        }

        private void btnIntegers_Click(object sender, EventArgs e)
        {
            numbers!.SetItem(2, 13579);

            lbxNumbers.Items.Clear();

            for (int i = 0; i < numbers.Count; i++)
                lbxNumbers.Items.Add(numbers[i]);
        }
    }

    public class Series<T> : Collection<T>
    {
        public Series() : base()
        {

        }

        public Series(IList<T> list) : base(list)
        {

        }

        public new IList<T> Items
        {
            get
            {
                return this;
            }
        }

        public new void Insert(int index, T item)
        {
            if (index < 0)
                throw new ArgumentOutOfRangeException("The value you provided " +
                                                      "for the index is not valid");
            else if( (index is >= 0) && (index is <= Count) )
                base.InsertItem(index, item);
            else
                // If the index is higher than Count, simply add the item (at the end of the list)
                this.Add(item);
        }

        public new void SetItem(int index, T item)
        {
            if (index is < 0)
                return;
            else if (index is > Count)
                return;
            else
                this[index] = item;

            // This too should work
            // base.SetItem(index, item);
        }

        public new void InsertItem(int index, T item)
        {
            base.InsertItem(index, item);
        }
    }
}

Another alternative is to add the item as the last in the list. This can be done as follows:

using System.Collections.ObjectModel;

namespace Hotel
{

    public class Rooms<T> : Collection<T>
    {
        public Rooms() : base()
        {
        }

        public Rooms(IList<T> list) : base(list)
        {
        }

        public virtual new IList<T> Items
        {
            get
            {
                return this;
            }
        }

        public new void InsertItem(int index, T item)
        {
            if(index is < 0)
                throw new ArgumentOutOfRangeException("The value you provided " +
                                                      "for the index is not valid");
            else if( (index is >= 0) & (index is <= Count))
                base.InsertItem(index, item);
            else  // If the index is higher than Count, simply add the item (at the end of the list)
                this.Add(item);
        }

        public new void SetItem(int index, T item)
        {
            if(index is < 0)
                return;
            else if(index is > Count)
                return;
            else
                base.SetItem(index, item);
        }
    }
}

Deleting Items

Removing an Item by its Index

To let you delete an item from its list, the Collection<> class provides a method named RemoveItem. Its syntax is:

protected virtual void RemoveItem(int index);

This method takes as argument the index of the item to be removed. Once again, a simple implementation would consist of calling the method from the base class. Here is an example;

using System.Collections.ObjectModel;

namespace Numerotation
{
    public partial class Exercise : Form
    {
        Series<int>? numbers;

        public Exercise()
        {
            InitializeComponent();
        }

        private void Exercise_Load(object sender, EventArgs e)
        {
            numbers = new Series<int>();

            numbers.Add(8);
            numbers.Add(937);
            numbers.Add(49);
            numbers.Add(384);
            numbers.Add(18497);
            numbers.Add(64);
            numbers.Add(228);

            Populate();
        }

        private void Populate()
        {
            lbxNumbers.Items.Clear();

            for (int i = 0; i < numbers!.Count; i++)
                lbxNumbers.Items.Add(numbers[i]);
        }

        private void btnDeleteSelectedItem_Click(object sender, EventArgs e)
        {
            if (lbxNumbers.SelectedIndex >= 0)
            {
                if(lbxNumbers.SelectedIndex == 0)
                    MessageBox.Show("The first item will be deleted.", "Numbers",
                                    MessageBoxButtons.OK, MessageBoxIcon.Information);
                else if (lbxNumbers.SelectedIndex == 1)
                    MessageBox.Show("The second item will be deleted.", "Numbers",
                                    MessageBoxButtons.OK, MessageBoxIcon.Information);
                else if (lbxNumbers.SelectedIndex == 2)
                    MessageBox.Show("The third item will be deleted.", "Numbers",
                                    MessageBoxButtons.OK, MessageBoxIcon.Information);
                else
                    MessageBox.Show("The " + (lbxNumbers.SelectedIndex + 1).ToString() + "th item will be deleted.",
                                    "Numbers", MessageBoxButtons.OK, MessageBoxIcon.Information);
                 
                numbers!.RemoveItem(lbxNumbers.SelectedIndex);     
                
                Populate();
            }
        }
    }

    public class Series<T> : Collection<T>
    {
        public new IList<T> Items
        {
            get
            {
                return this;
            }
        }

        public new void SetItem(int index, T item)
        {
            base.SetItem(index, item);
        }

        public new void RemoveItem(int index)
        {
            base.RemoveItem(index);
        }
    }
}

Here is an example of running the program:

Collection

Collection Collection Collection
Collection Collection Collection

Removing an Item At a Position

As another way to delete an item from a list, the Collection<> class is equipped with a method named RemoveAt. Its syntax is:

public void RemoveAt(int index);

This method is public. This means that it is ready out of the box. If you are defining this method, the easiest way to do it is to call the base implementation. Here is an example:

public new void RemoveItem(int index)
{
    base.RemoveAt(index);
}

For both the Collection<>.RemoveItem() and the Collection<>.RemoveAt() method, when implementing the method, you can decide what to do if the index is negative or is higher than the total number of items in the list. Also, you can specify what other actions the compiler should take when an item is being deleted. Here is an example:

using System.Collections.ObjectModel;

namespace Hotel
{
    public class Rooms<T> : Collection<T>
    {
        . . .

        public new void RemoveItem(int index)
        {
            if(index is < 0)
                return;
            else if(index is > Count)
                return;
            else
                base.RemoveItem(index);
        }
    }
}

Clearing a Collection

Because the Collection<> class implements the ICollection interface, it inherits the Clear() method. To let you create a custom technique to delete all items from a class, the Collection<> class is equipped with a protected method named ClearItems. Its syntax is:

protected virtual void ClearItems();

When overriding this method, if you don't have any particular way you want to delete items, you can simply call the Clear() method. Otherwise, define the method as you wish. Here is an example:

using System.Collections.ObjectModel;

namespace Numbers
{
    public partial class Exercise : Form
    {
        Series<string>? states;

        public Exercise()
        {
            InitializeComponent();
        }

        private void Exercise_Load(object sender, EventArgs e)
        {
            states = new Series<string>();

            Populate();
        }

        private void Populate()
        {
            lbxStates.Items.Clear();

            foreach(string state in states!)
                lbxStates.Items.Add(state);

            lblCount.Text = states.Count.ToString();
        }

        private void btnAdd_Click(object sender, EventArgs e)
        {
            if( !string.IsNullOrEmpty(txtState.Text) )
            {
                states!.Insert(states.Count, txtState.Text);
            }

            txtState.Text = string.Empty;
            Populate();

            txtState.Focus();
        }

        private void btnInsertAfterSelectedItem_Click(object sender, EventArgs e)
        {
            if( !string.IsNullOrEmpty(txtState.Text) )
            {
                states!.InsertItem(lbxStates.SelectedIndex, txtState.Text);
            }

            txtState.Text = string.Empty;
            Populate();

            txtState.Focus();
        }

        private void btnDeleteSelectedItem_Click(object sender, EventArgs e)
        {
            if (lbxStates.SelectedIndex >= 0)
            {
                MessageBox.Show(lbxStates.SelectedItem.ToString() + " will be deleted.", "Numbers",
                                    MessageBoxButtons.OK, MessageBoxIcon.Information);
                
                states!.RemoveItem(lbxStates.SelectedIndex);     
                
                Populate();
            }
        }

        private void btnDeleteAllItems_Click(object sender, EventArgs e)
        {
            states!.ClearItems();

            Populate();
        }
    }

    public class Series<T> : Collection<T>
    {
        public Series() : base()
        {

        }

        public Series(IList<T> list) : base(list)
        {

        }

        public new IList<T> Items => this;

        public new void SetItem(int index, T item)
        {
            if (index is < 0)
                return;
            else if (index > Count)
                return;
            else
                base.SetItem(index, item);
        }

        public new void Insert(int index, T item)
        {
            if(index is < 0)
                throw new ArgumentOutOfRangeException("The value you provided " +
                                                      "for the index is not valid");
            else if( (index >= 0) & (index <= Count) )
                base.InsertItem(index, item);
            else // If the index is higher than Count, simply add the item (at the end of the list)
                this.Add(item);
        }

        public new void InsertItem(int index, T item)
        {
            if( (index is < 0) | (index > this.Count) )
                return;
            else
                base.InsertItem(index, item);
        }

        public new void RemoveItem(int index)
        {
            if( (index is < 0) || (index > this.Count) )
                return;
            else
                base.RemoveItem(index);
        }

        public new void ClearItems()
        {
            if(this.Count is < 0)
                return;
            else
                base.Clear();
        }
    }
}

Collection

Practical LearningPractical Learning: Ending the Lesson


Previous Copyright © 2014-2023, FunctionX Monday 06 December 2021 Home