Although you can create a collection class from scratch, most collections classes share some fundamental behaviors and characteristics that you can apply when creating a collection class. To give a skeleton of these default requirements, the .NET Framework provides a few interfaces that you can implement.

Practical Learning: Introducing .NET Collection Classes

Start Microsoft Visual Studio

On the main menu, click File -> New -> Project ...

In the middle frame of the New Project dialog box, click ASP.NET Web Application (.NET Framework) and change the project Name to WaterDistributionCompany1

Click OK

In the New ASP.NET Application dialog box, click the MVC icon and click OK

In the Solution Explorer, under Views, expand Home, and double-click Index.cshtml

Change the document as follows:

@{
ViewBag.Title = "Welcome";
}
<div class="jumbotron">
<h2>.</h2>
<p class="lead">Our water utility company provides energy, greatness, and warmth
for a everyday life, a shining life. We provide solutions to families, businesses,
and the community.</p>
<p class="lead">This is the employees portal section of the company. From here,
employees can register a new water meter, manage a customer account, or
create a water bill.</p>
</div>
<div class="row">
<div class="col-md-4">
<h2>Water Meters</h2>
<p>Our company uses the most accurate, sophisticated, and environment-friendly
water meters on the market. Our suppliers care.</p>
<p>@Html.ActionLink("New Water Meter", "NewWaterMeter", "Home", new { @class = "btn btn-primary" })</p>
</div>
<div class="col-md-4">
<h2>Customers</h2>
<p>We supply water to individuals, families, small
businesses, as well as enterprises or government agencies.</p>
<p>@Html.ActionLink("New Customer Account", "NewCustomer", "Home", new { @class = "btn btn-primary" })</p>
</div>
<div class="col-md-4">
<h2>Water Bills</h2>
<p>Our water rates are very competitive nationwide. We use precise,
effective, and strict algorithms when performing our water bills
calculations.</p>
<p>@Html.ActionLink("New Water Bill", "FindCustomer", "Home", new { @class = "btn btn-primary" })</p>
</div>
</div>

To preview the result, on the main menu, click Debug -> Start Without Debugging:

Close the browser and return to your programming environment

Introduction to the ICollection Interface

One of the primary pieces of information you should provide abo ut the values in a collection is the number of items a list is (currently) holding. When creating a collection class, to prepare it to provide this valuable information, you can (should) implement an interface named ICollection. The ICollection interface is defined in the System.Collections namespace. The ICollection interface derives from the IEnumerable initerface:

public interface ICollection : IEnumerable

The generic equivalent of the ICollection interface is defined in the System.Collections.Generic namespace. The
ICollection<> interface inherits from the IEnumerable<> and the IEnumerable interfaces:

public interface ICollection<T> : IEnumerable<T>,
IEnumerable

Therefore, to start a collection class, you can first implement one of these interfaces. Here is an example for the System.Collections.ICollection<> interface:

To assist you with keeping track of the number of items in a collection, the ICollection<T> interface is equipped with an integral property named Count, which you must implement. To do this, you can create a private member variable that will actually keep a count of the number of items. The Count property can then be used to communicate this information to the clients of the class. Here is an example:

The ICollection<> interface also allows its implementer to copy some of its items to an array. To provide this functionality, the interface is equipped with a method named CopyTo, which you must implement. The syntax of this method is:

void CopyTo(T[] array, int arrayIndex);

This method takes two arguments. The first argument is the array that will receive the items. The second argument is the index of the item from where the copying operation will begin. Here is an example:

The System.Collections.ICollection interface extends the IEnumerable interface. The System.Collections.Generic.ICollection<T> interface extends the IEnumerable and the IEnumerable<> interfaces. In the previous lesson, we saw that this means that you should be able to use foreach in your ICollection<>-based collections but you must create the functionality yourself, which is done by implementing the GetEnumerator() method. Because the ICollection<> interface inherits from IEnumerable<> that itself inherits from IEnumerable, you must implement two versions of the GetEnumerator() methods. As we saw already, their syntaxes are:

IEnumerator<T> GetEnumerator();
IEnumerator GetEnumerator();

Even if you don't want to support this feature, you still must provide at least a skeleton for these methods. Here is an example:

While the System.Collections.Generic.ICollection<> (and the System.Collections.ICollection) interface provides basic functionality, to assist you in laying out a foundation of a collection class as complete as possible, the .NET Framework provides an interface named IList<> (and its accompanying IList interface). The IList<> interface is defined in the System.Collections.Generic namespace and its
non-generic equivalent of the same name is defined in the System.Collections namespace. To use the functionalities of the System.Collections.Generic.IList<> interface necessary to add, insert, get, and delete items from a collection, you must create a class that implements them.

This means that the IList<> interface extends the ICollection<>, the IEnumerable<>, and the IEnumerable interfaces. This also implies that you must implement the members of all these parent interfaces, including the Count, the IsReadOnly properties, the CopyTo(), and the GetEnumerator() methods as we saw above. From what we learned with the ICollection interface, here are examples of
implementing these members for the System.Collections.IList<T> interface:

As you may know already, while you are implementing an interface, if you are working in Microsoft Visual Studio, it would keep displaying some warnings to remind you about some member(s) you haven't implemented yet. Microsoft Visual Studion can generate skeleton code for your implementation. This would accomplish two goals: 1) it would eliminate the warnings (and errors), 2) it would provide a type of default implementation or a member.

To ask Microsoft Visual Studio to generate code for your interface implementation, start the class in the Code Editor and specify its interface(s). Then:

Right-click the underlined name of an (or the) interface and click Quick Actions and Refactorings... In the window that appears, click Preview Changes. In the Preview Changes dialog box, click Apply

Position the mouse on the underlined name and click the arrow button on the right side of the orange lamp. In the menu that appears, make your selection

In the window that appears, click Implement Interface (if another window appears, in the Preview Changes window, click Apply)

To avoid C# 7 errors (or issues), and to improve the code, change the document as follows:

using System;
using System.Collections;
using System.Collections.Generic;
namespace WaterDistributionCompany3.Models
{
[Serializable]
public class Collection<T> : IList<T>
{
// This field will hold the number of items as they are added or removed
private int counter;
// This array holds the items of the collection
private T[] items;
// The default constructor is used to initialize a constructor with some default values.
public Collection()
{
counter = 0;
// We will start the collection with a capacity for 10 items
items = new T[10];
}
/* IList<T>: This indexed property makes it possible to access EACH item as done in an array. */
public T this[int index]
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
/* ICollection<T>: This read-only property holds the total number of items in the collection*/
public int Count
{
get
{
return counter;
}
}
/* ICollection<T>: This property would prevent new item addition.*/
public bool IsReadOnly
{
get
{
throw new NotImplementedException();
}
}
/* ICollection<T>: This method is used to add a new item to the collection.*/
public void Add(T item)
{
throw new NotImplementedException();
}
// ICollection<T>: This method deletes all items from the collection
public void Clear()
{
throw new NotImplementedException();
}
/* ICollection<T>: This method is used to find out whether
* the collection contains the item passed as argument. */
public bool Contains(T item)
{
throw new NotImplementedException();
}
/* ICollection<T>: This method is used to copy
* the items of this collection to an array passed as argument. */
public void CopyTo(T[] array, int arrayIndex)
{
throw new NotImplementedException();
}
/* IEnumerable<T>: Used for iteration */
public IEnumerator<T> GetEnumerator()
{
throw new NotImplementedException();
}
/* IList<T>: This method checks whether the item
* passed as argument exists in the collection. If so, it returns its index. */
public int IndexOf(T item)
{
throw new NotImplementedException();
}
/* IList<T>: This method can be used to insert an item at a certain position inside the collection. */
public void Insert(int index, T item)
{
throw new NotImplementedException();
}
/* ICollection<T>: This method first checks the existence of
* the item passed as argument. If the item exists, the method deletes it. */
public bool Remove(T item)
{
throw new NotImplementedException();
}
/* IList<T>: This method can be used to delete the item positioned at the index passed as argument. */
public void RemoveAt(int index)
{
throw new NotImplementedException();
}
/* IEnumerable: Used for iteration */
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
}
}

Populating a Collection

A Read-Only Collection

Most collections are made to receive new values. If you want, you can create a list that cannot receive new values. To support this, the IList interface is equipped with a Boolean property named IsReadOnly. If a list is read-only, it would prevent the clients from adding items to it.

Here is an example of implementing the IsReadOnly property for the System.Collections.IList<T> interface:

As it should be obvious, the primary operation to perform on a list is to populate it with at least one value or object. To support this operation, the IList<> interface is equipped with a method named Add. Its syntax is:

void Add(T item);

This method takes one argument as the value to add to the collection. If your collection is an array, you can first check that there is still enough room to add a new item. In reality, this is never an issue with the System.Collections.IList interface:

If there is still room in the collection, the value would be added to the list

By default, an array has a fixed size. If you try adding an item in a position higher than the maximum number of items, the compiler would throw an IndexOutOfRangeException exception. Fortunately, we saw that the Array class is equipped with a method named Resize. This allows you to increase the size of an array when you judge it necessary. As a consequence, you can have an array as big as possible.

Practical Learning: Adding an Item to the Collection

Change the code as follows:

using System;
using System.Collections;
using System.Collections.Generic;
namespace WaterDistributionCompany3.Models
{
[Serializable]
public class Collection<T> : IList<T>
{
. . . No Change
private void CheckAndIncreaseIfNecessary()
{
// If there is not enough room to add a new item (low capacity), create room for 5 additional items.
if (counter >= items.Length)
Array.Resize<T>(ref items, items.Length + 5);
}
/* ICollection<T>: This method is used to add a new item to the collection.*/
public void Add(T item)
{
// Check whether there is still room in the array.
// If there is not enough room, then increase the capacity
CheckAndIncreaseIfNecessary();
// Add the item at the end
items[counter] = item;
// Increase the current number of items
counter++;
}
. . . No Change
}
}

To execute the project, on the main menu, click Debug -> Start Without Debugging:

On the top menu of the web page, click Home and click New Water Meter

Type the values on each row of the following table to create a record and click Submit when you have finished each:

Meter #

Make

Model

Meter Size

293-740

Breston

S-93749

3/4 Inches

820-418

Vashty Worldwide

DD-3840

3/4 Inches

Close the browser and return to your programming environment

Inserting an Item

When you call the Add() method, it adds the new value to the end of the list. Sometimes, you will want the new value to be inserted somewhere inside the list. To support this operation, both the System.Collections.IList and the System.Collections.Generic.IList interfaces provide a method named Insert. The syntax of the System.Collections.IList.Insert() method is:

void Insert(int index, object value);

The syntax of the System.Collections.Generic.IList.Insert() method is:

void Insert(int index, T value);

This method takes two arguments. The second argument is the value or object that will be inserted into the list. The argument must hold a valid value or object. Because the System.Collections.IList.Insert() method takes an object value, if your collection is using a different type of value, you may have to cast it to object. The first argument is the index of the item that will precede the new one.

As mentioned for the System.Collections.IList.Add() method, there are a few things you should know about this operation's success or failure:

If the index argument holds a negative value or a value higher than the allowed number (for example if the list is a fixed
array) of the items (depending on how you implement the method), the new value would not be added, the compiler would not throw an exception, and therefore nothing would let you know that the value was not added. If you want to find out whether the value was formally or actually inserted or not, you must create the functionality yourself

If the value argument is not valid, again depending on how you create your class, either the value would not be inserted or something else would go wrong. Fortunately, if the value argument is of a type of a class you created yourself, the compiler would produce an error such as letting you know that the argument is holding a value that is not conform to its property or member

The System.Collections.Generic.IList.Insert() method can be implemented as follows:

using System.Collections.Generic;
namespace Exercises.App_Code
{
public class Itemization<T> : IList<T>
{
. . . No Change
public void Insert(int index, T value)
{
// If the index is the same as the current number of items
// then call Add() and proceed as if we want to add a new item
if (index >= size)
{
Add(value);
return;
}
// If the index is positive but less than the current size,
// then you need to insert the item inside the collection.
if ((index >= 0) && (index < size))
{
// First, push each item one position up to create
// an empty space
for (int i = (size - 1); i > index - 1; i--)
items[i + 1] = items[i];
// Then put the new item in the indicated position
items[index] = value;
// Since the new item has been added, increase the
// current number of items
size++;
}
}
public void CopyTo(T[] array, int index)
{
. . . No Change
}
. . . No Change
}
}

When calling the IList<>.Insert() method, if you pass an invalid index, the compiler would throw an ArgumentOutOfRangeException.

Locating an Item in the Collection

this Item of a Collection

While using a list, various operations require that you know the object you are currently accessing. To support this operation, the IList and the IList<> interfaces are equipped with an indexed property named Item that you must implement. The Item
property of the System.Collection.Generic.IList interface is declared as follows:

T this[int index] { get; set; }

If you implement it right, this property can perform one of three operations:

If you pass an index that is the same number as the items in the collection and assign a value to it, it allows you to add an item at the end of the collection

If you give it a positive index that is lower than the total number of items in the list and assign a value to it, it replaces the item at that position with the new value

It you get the value at a positive index that is lower than the total number, it produces the item at that position

Here is an example of implementing the indexed property of the IList<> interface:

public class Itemization<T> : IList<T>
{
private int size;
private T[] items;
. . .
// The index property can be used to add an item to a collection,
// or to get an item from the collection
public T this[int index]
{
get
{
return this.items[index];
}
set
{
this.items[index] = value;
}
}
}

After creating this property, you can then access an item using its index and applying the [] operator on its instance.

Practical Learning: Identifying this Item in the Collection

In the Solution Explorer, under App_Code, double-click Colection.cs to access it

In the top-right combo box of the Code Editor, select this[int index] and change the code of the property as follows:

One of the most valuable operations to make available to a collection is the abilite to visit each one of its members. As we saw already, this operation is supported by the GetEnumerator() method
provided by the IEnumerable<T> interface that each collection class implements.

using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Web.Mvc;
namespace WaterDistributionCompany10.Controllers
{
public class WaterMetersController : Controller
{
// GET: WaterMeters
public ActionResult Index()
{
return View();
}
// GET: WaterMeters/Create
public ActionResult Create(string MeterNumber, string MeterSize, string Make, string Model)
{
FileStream fsWaterMeters = null;
BinaryFormatter bfWaterMeters = new BinaryFormatter();
string strFileWaterMeters = Server.MapPath("~/App_Data/WaterMeters.mtr");
Models.Collection<Models.WaterMeter> waterMeters = new Models.Collection<Models.WaterMeter>();
// Make sure a meter number was provided. If not, don't do nothing
if (!string.IsNullOrEmpty(MeterNumber))
{
// Create a water meter based on the values gotten from the form
Models.WaterMeter meter = new Models.WaterMeter()
{
MeterNumber = MeterNumber,
Make = Make,
Model = Model,
MeterSize = MeterSize
};
// If a list of water meters was previously created and saved to a file...
if (System.IO.File.Exists(strFileWaterMeters))
{
// ... open that file ...
using (fsWaterMeters = new FileStream(strFileWaterMeters, FileMode.Open, FileAccess.Read, FileShare.Read))
{
// ... get the list of water meters
waterMeters = (Models.Collection<Models.WaterMeter>)bfWaterMeters.Deserialize(fsWaterMeters);
}
}
// Whether the list of water meters is empty or not, add the new water meter to the list
waterMeters.Add(meter);
// Save the new list of water meters to a file
using (fsWaterMeters = new FileStream(strFileWaterMeters, FileMode.Create, FileAccess.Write, FileShare.Write))
{
bfWaterMeters.Serialize(fsWaterMeters, waterMeters);
}
// In case the user wants to create a new water meter, send him/her back to the form
return RedirectToAction("Create");
}
return View();
}
}
}

To execute the project, on the main menu, click Debug -> Start Without Debugging:

Enter the values from each row of the following table and click Submit each time to create the record:

Account #

Meter #

First Name

Last Name

Address

City

County

State

ZIP Code

2958-314-5294

627-425

Nicholas

Thorn

2599 Phenicia Road

Silver Spring

Montgomery

MD

20906

8046-728-5060

304-861

Augustino

Derbez

7507 Westchester Ave

Washington

DC

20008

1848-205-3313

925-935

Frank

Nunka

13931 Wellington Street

College Park

Pringe George

MD

20740

4024-850-0482

820-418

Marion

Patters

10572 Maya Blvd

Frederick

MD

20111

7029-371-8594

293-740

Danielle

Dormand

2515 Guthierez Street

Falls Church

VA

22046

Close the browser and return to your programming environment

Checking Whether a Collection Contains a Certain Item

One of the routine operations you can perform on a list is to find out whether it contains a certain value. To assist you with this operation, the IList<> interface is equipped with a method named Contains. Its syntax is:

bool Contains(T item);

This method takes as argument the value to look for. If the value is found in the collection, the method returns true. If the value is not found in the collection, this method returns false. Here is an example of implementing this method:

This method calls the Equals() method of the objects that make up the list to find out whether the value argument exists in the collection. If this method produces a wrong result, especially if you are using your own class to represent the item, you should (must) override the Equals() method. Here is an example of a class that indicates that two Employee objects are the same if they have the same employee number (because two employees in the same company/collection should not have the same employee number:

The Contains() method is used to check whether a particular value (already) exists in the collection. If you know that a certain item exists but you don't know its index inside the list, the IList<> interface can assist you through a method named IndexOf. Its syntax is:

int IndexOf(T item);

This method takes as argument the value to look for in the list. If the value is found in the collection, the method returns its
index. If there is no value defined like that, the method returns -1. Here is an example of implementing this method:

Getting an item consists of finding and producing it, not necessarily its index or whether it exists. You can write code in your collection class to get the object or you can use a combination of the methods and properties we have already reviewed to locate and produce the item.

In the Solution Explorer, under Views, right-click Home -> Add -> View...

Type SaveWaterBill as the name of the view

Click Add

Click the WaterBillStartUp.cshtml tab to activate it

To execute the project, on the main menu, click Debug -> Start Without Debugging:

In the text boxes, enter some values as follows:

Account #

1848-205-3313

Service Start Date

1/12/2018

Service To

4/17/2018

Meter Reading Start

274.68

Reading End

316.53

Click the Create Watter Bill button:

Verify the bill summary and click the Save Water Bill button

Enter some values as follows:

Account #

2958-314-5294

Service Start Date

1/18/2018

Service To

4/17/2018

Meter Reading Start

5827.59

Reading End

5883.17

Click Create Water Bill:

Click the Save Water Bitt button

In the text boxes, enter some values as follows:

Account #

4024-850-0482

Service Start Date

4/14/2018

Service To

7/6/2018

Meter Reading Start

1436.64

Reading End

1454.86

Click the Create Water Bill button

Click the Save Water Bill button

Close the browser and return to your programming environment

Items Maintenance in a Collection

Editting an Item

Editing an item consists of changing its value or one of its details. The IList interface doesn't provide its own method to do this. The reason is that it depends on the item and what you are trying to do. For example, if the collection is made of value types or strings, it is easy to change the value. If the collection is made of objects, then you must decide what value of a property you want to change, and this can be unpredictable. As done in formal database, the best steps to follow are:

Locate (find) the desired record: Use any technique or approach you want to get the record. In some case, it may also be helpful to get its index (in databases, this would be something called the primary key)

Change anything (any part) you want on (of) the record

Save the record: In most cases, this would be equivalent to replacing the (previous version of) record with the new values (the new version of the record)

Deleting a Value by its Index

If a value is not necessary in your collection, you can delete it. Probably the simplest way to delete a value is to specify its position in the list. To support this operation, both the System.Collections.IList and the System.Collections.Generic.IList interfaces are equipped with a method named RemoveAt. The syntax of the RemoveAt() method is the same for both interfaces and it is:

void RemoveAt(int index);

This method takes as argument the index of the value to be removed. If the index is valid, the method removes the item at that position. If the index is not valid, the compiler would throw an ArgumentOutOfRangeException exception. Here is an example:

public class Itemization<T> : IList<T>
{
private int size;
private T[] items;
. . .
// This method is used to delete an item based on its position
public void RemoveAt(int index)
{
// First check that the user provided a positive index that
// is lower than the current total number of items
if ((index >= 0) && (index < size))
{
// If so, starting at that index, get the item ahead of
// each position and assign it to the current item
for (int i = index; i < size - 1; i++)
items[i] = items[i + 1];
// Since the last item is not useful anymore,
// decrease the number of items
size--;
}
}
}

If the item cannot be deleted for another reason, the compiler would throw a NotSupportedException exception.

Deleting an Item by its Value

The above method could deleting the wrong value or object if you provide the wrong index. An alternative is to first precisely define the value or the object you want to get rid of, and then hand the value or object to the compiler that would remove it. To support this approach, the System.Collections.IList interface is equipped with a method named Remove. Its syntax is:

bool Remove(T item);

This method takes as argument the value to be deleted. This means that the compiler will first look for the value in the list. If it finds that value, it removes it. If there is no value like that, nothing would happen. Here is an example of implementing this method:

public class Itemization<T> : IList<T>
{
private int size;
private T[] items;
. . .
// This method is used to delete an item
public bool Remove(T value)
{
// First try to get the index of the item to find out if it exists
int index = IndexOf(value);
// If the item was found ...
if (index >= 0)
{
// ... delete it, using its index
RemoveAt(index);
// Since the item has been removed, return true
return true;
}
// If the item was not removed (for any reason), return false
return false;
}
}

Clearing a Collection

To let you remove all values or objects from a list, the IList<> interface is equipped with a method named Clear. Its syntax is:

void Clear();

Here is an example of implementing it:

public class Itemization<T> : IList<T>
{
private int size;
private T[] items;
public Itemization()
{
size = 0;
items = new T[10];
}
public void CopyTo(T[] array, int arrayIndex)
{
T[] values = new T[size];
for (int i = 0; i < size; i++)
values[i] = items[i];
array = values;
}
public int Count
{
get { return size; }
}
public bool IsReadOnly
{
get { return false; }
}
private void CheckAndIncreaseIfNecessary()
{
if (size >= items.Length)
Array.Resize<T>(ref items, items.Length + 5);
}
// The index property can be used to add an item to a collection,
// or to retrieve an item from the collection
public T this[int index]
{
get
{
return this.items[index];
}
set
{
this.items[index] = value;
}
}
public void Add(T value)
{
// Check whether there is still room in the array.
// If there is not enough room, then increase the capacity
CheckAndIncreaseIfNecessary();
// Add the item at the end
this.items[this.size] = value;
// Increase the current number of items
this.size++;
}
public void Insert(int index, T value)
{
// If the index is the same as the current number of items
// then call Add() and proceed as if we want to add a new item
if (index >= size)
{
Add(value);
return;
}
// If the index is positive but less than the current size,
// then you need to insert the item inside the collection.
if ((index >= 0) && (index < size))
{
// Firs, push each item one position up to create
// an empty space
for (int i = (size - 1); i > index - 1; i--)
items[i + 1] = items[i];
// Then put the new item in the indicated position
items[index] = value;
// Since the new item has been added, increase the
// current number of items
size++;
}
}
public bool Contains(T value)
{
for (int i = 0; i < Count; i++)
if (items[i].Equals(value))
return true;
return false;
}
public int IndexOf(T value)
{
for (int i = 0; i < Count; i++)
if (items[i].Equals(value))
return i;
return -1;
}
// This method is used to delete an item based on its position
public void RemoveAt(int index)
{
// First check that the user provided a positive index that
// is lower than the current total number of items
if ((index >= 0) && (index < size))
{
// If so, starting at that index, get the item ahead of
// each position and assign it to the current item
for (int i = index; i < size - 1; i++)
items[i] = items[i + 1];
// Since the last item is not useful anymore,
// decrease the number of items
size--;
}
}
// This method is used to delete an item
public bool Remove(T value)
{
// First try to get the index of the item to find out if it exists
int index = IndexOf(value);
// If the item was found ...
if (index >= 0)
{
// ... delete it, using its index
RemoveAt(index);
// Since the item has been removed, return true
return true;
}
// If the item was not removed (for any reason), return false
return false;
}
// This method is used to delete all items from a collection
public void Clear()
{
// Visit each item in the collection and set it to the default value
// (This is not really necessary)
for (int i = 0; i < size; i++)
items[i] = default(T);
// Reset the number of items of the collection to 0
size = 0;
}
public IEnumerator<T> GetEnumerator()
{
int counter = 0;
while (counter < Count)
{
yield return items[counter];
counter++;
}
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
int counter = 0;
while (counter < Count)
{
yield return items[counter];
counter++;
}
}
}