A Generic Collection

Introduction

Consider the following class from our previous lesson:

using static System.Console;

public class Collection
{
    string[] cities;
    int size;

    public Collection()
    {
        size = 0;
        cities = new string[6];
    }

    public void Add(string city)
    {
        cities[size] = city;
        size++;
    }

    public string this[int index]
    {
        get { return cities[index];  }
        set { cities[index] = value; }
    }

    public int Count => size;
}

public class Exercise
{
    public static void Main()
    {
        Collection names = new Collection();
        names.Add("Ottawa");
        names.Add("Accra");
        names.Add("Jakarta");
        names.Add("Bonn");
        names.Add("Mexico City");
        names.Add("Brisbane");

        WriteLine("Cities");
        WriteLine("-------------------");
        WriteLine("City: " + names[0]);
        WriteLine("City: " + names[1]);
        WriteLine("City: " + names[2]);
        WriteLine("City: " + names[3]);
        WriteLine("City: " + names[4]);
        WriteLine("City: " + names[5]);
        WriteLine("-------------------");
        WriteLine("Collection Size: " + names.Count);

        WriteLine("================================");
        return;
    }
}

This would produce:

Cities
-------------------
City: Ottawa
City: Accra
City: Jakarta
City: Bonn
City: Mexico City
City: Brisbane
-------------------
Collection Size: 6
================================
Press any key to continue . . .

In the above class, you can create a collection of only strings and not other type. In the real world, you should be able to create a collection of any type. Your probable first thought is to create a different collection class for each type. This is not professional because there are as many types as sand on the floor. A professional alternative is to create a generic class that can be used to create a collection of any type.

As you may know already, to create a generic class, you can add <> to its name. The class can start as follows:

public class Collection<T>
{

}

After doing it, and based on our Collection class, throughout the class, you can substitute our string type with the parameter type. Here is an example:

public class Collection<GenericType>
{
    GenericType[] cities;
    int size;

    public Collection()
    {
        size = 0;
        cities = new GenericType[6];
    }

    public void Add(GenericType city)
    {
        cities[size] = city;
        size++;
    }

    public GenericType this[int index]
    {
        get { return cities[index];  }
        set { cities[index] = value; }
    }

    public int Count => size;
}

When declaring a variable for the class, you should (must) add or specify a parameter type. Here is an example:

using static System.Console;

public class Collection<GenericType>
{
    GenericType[] cities;
    int size;

    public Collection()
    {
        size = 0;
        cities = new GenericType[6];
    }

    public void Add(GenericType city)
    {
        cities[size] = city;
        size++;
    }

    public GenericType this[int index]
    {
        get { return cities[index];  }
        set { cities[index] = value; }
    }

    public int Count => size;
}

public class Exercise
{
    public static void Main()
    {
        Collection<string> names = new Collection<string>();
        names.Add("Ottawa");
        names.Add("Accra");
        names.Add("Jakarta");
        names.Add("Bonn");
        names.Add("Mexico City");
        names.Add("Brisbane");

        WriteLine("Cities");
        WriteLine("-------------------");
        WriteLine("City: " + names[0]);
        WriteLine("City: " + names[1]);
        WriteLine("City: " + names[2]);
        WriteLine("City: " + names[3]);
        WriteLine("City: " + names[4]);
        WriteLine("City: " + names[5]);
        WriteLine("-------------------");
        WriteLine("Collection Size: " + names.Count);

        WriteLine("================================");
        return;
    }
}

You may remember that, by tradition, the parameter type is named T. This would be done as follows:

public class Collection<T>
{
    T[] cities;
    int size;

    public Collection()
    {
        size = 0;
        cities = new T[6];
    }

    public void Add(T city)
    {
        cities[size] = city;
        size++;
    }

    public T this[int index]
    {
        get { return cities[index];  }
        set { cities[index] = value; }
    }

    public int Count => size;
}

A Generic Enumerable Collection

Instead of creating a generic collection class from scratch, the .NET Framework provides a generic interface named IEnumerable. This interface is inherits from an interface of the same name from the System.Collections namespace. The System.Collections.Generic.IEnumerable interface starts as follows:

public interface IEnumerable<out T> : System.Collections.IEnumerable

To benefit from this interface, create a class that implements it. Both the name of the class and the interface must have parameter type. The class would start as follows:

using System.Collections.Generic;

public class Collection<T> : IEnumerable<T>
{

}

The System.Collections.Generic.IEnumerable<> interface is equipped with only one member, which is a method named GetEnumerator. Your class must implement that method. Even if you are not planning to use it, you must provide at least a skeleton code for it. This can be done as follows:

using System.Collections;
using System.Collections.Generic;

public class Collection<T> : IEnumerable<T>
{
    IEnumerator<T> IEnumerable<T>.GetEnumerator()
    {
        throw new System.NotImplementedException();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        throw new System.NotImplementedException();
    }
}

We also know that you can start the items of a collection class with an array. This can be done as follows:

using System.Collections;
using System.Collections.Generic;

public class Collection<T> : IEnumerable<T>
{
    T[] items;

    public Collection()
    {
        items = new T[5];
    }

    IEnumerator<T> IEnumerable<T>.GetEnumerator()
    {
        throw new System.NotImplementedException();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        throw new System.NotImplementedException();
    }
}

From what we have learned so far about collection classes, you should provide a way to count the items of the collection. This can be taken care of as follows:

using System.Collections;
using System.Collections.Generic;

public class Collection<T> : IEnumerable<T>
{
    T[] items;
    int size;

    public Collection()
    {
        size = 0;
        items = new T[5];
    }

    public virtual int Count
    {
        get { return size; }
    }

    IEnumerator<T> IEnumerable<T>.GetEnumerator()
    {
        throw new System.NotImplementedException();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        throw new System.NotImplementedException();
    }
}

You should also provide a way to add items to a collection. Here is an example:

using System.Collections;
using System.Collections.Generic;

public class Collection<T> : IEnumerable<T>
{
    T[] items;
    int size;

    public Collection()
    {
        size = 0;
        items = new T[5];
    }

    public virtual int Count
    {
        get { return size; }
    }

    public void Add(T item)
    {
        items[size] = item;
        size++;
    }

    IEnumerator<T> IEnumerable<T>.GetEnumerator()
    {
        throw new System.NotImplementedException();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        throw new System.NotImplementedException();
    }
}

public class Exercise
{
    public static void Main()
    {
        Collection<string> names = new Collection<string>();

        names.Add("Bamako");
        names.Add("San Francisco");
        names.Add("Minsk");
        names.Add("Tokyo");
        names.Add("La Paz");
    }
}

You should also provide a way to get or retrieve a value from an object of the class. You can do this with a Get() method. Here is an example:

using System.Collections;
using static System.Console;
using System.Collections.Generic;

public class Collection<T> : IEnumerable<T>
{
    T[] items;
    int size;

    public Collection()
    {
        size = 0;
        items = new T[5];
    }

    public virtual int Count
    {
        get { return size; }
    }

    public void Add(T item)
    {
        items[size] = item;
        size++;
    }

    public T Get(int index)
    {
        return items[index];
    }

    IEnumerator<T> IEnumerable<T>.GetEnumerator()
    {
        throw new System.NotImplementedException();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        throw new System.NotImplementedException();
    }
}

public class Exercise
{
    public static void Main()
    {
        Collection<string> names = new Collection<string>();

        names.Add("Bamako");
        names.Add("San Francisco");
        names.Add("Minsk");
        names.Add("Tokyo");
        names.Add("La Paz");

        WriteLine("Cities");
        WriteLine("-------------------");
        WriteLine("City: " + names.Get(0));
        WriteLine("City: " + names.Get(1));
        WriteLine("City: " + names.Get(2));
        WriteLine("City: " + names.Get(3));
        WriteLine("City: " + names.Get(4));
        WriteLine("---------------------");
        WriteLine("Collection Size: " + names.Count);

        WriteLine("================================");
        return;
    }
}

This would produce:

Cities
-------------------
City: Bamako
City: San Francisco
City: Minsk
City: Tokyo
City: La Paz
---------------------
Collection Size: 5
================================
Press any key to continue . . .

Or, better, you can create a this indexer. This can be done as follows:

using System.Collections;
using static System.Console;
using System.Collections.Generic;

public class Collection<T> : IEnumerable<T>
{
    T[] items;
    int size;

    public Collection()
    {
        size = 0;
        items = new T[5];
    }

    public virtual int Count
    {
        get { return size; }
    }

    public void Add(T item)
    {
        items[size] = item;
        size++;
    }

    public T Get(int index) { return items[index]; }

    public T this[int index]
    {
        get { return items[index]; }
        set { items[index] = value; }
    }

    IEnumerator<T> IEnumerable<T>.GetEnumerator()
    {
        throw new System.NotImplementedException();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        throw new System.NotImplementedException();
    }
}

public class Exercise
{
    public static void Main()
    {
        Collection<string> names = new Collection<string>();

        names[0] = "Bamako";
        names[1] = "San Francisco";
        names[2] = "Minsk";
        names[3] = "Tokyo";
        names[4] = "La Paz";

        WriteLine("Cities");
        WriteLine("-------------------");
        WriteLine("City: " + names[0]);
        WriteLine("City: " + names[1]);
        WriteLine("City: " + names[2]);
        WriteLine("City: " + names[3]);
        WriteLine("City: " + names[4]);
        WriteLine("---------------------");
        WriteLine("Collection Size: " + names.Count);

        WriteLine("================================");
        return;
    }
}

Enumerating an Enumerable

If you have an Add() method and an indexer, you can use a loop to access each item of the collection, in which case each item can be accessed by its index. Here is an example:

using System.Collections;
using static System.Console;
using System.Collections.Generic;

public class Collection<T> : IEnumerable<T>
{
    T[] items;
    int size;

    public Collection()
    {
        size = 0;
        items = new T[5];
    }

    public virtual int Count
    {
        get { return size; }
    }

    public void Add(T item)
    {
        items[size] = item;
        size++;
    }

    public T Get(int index) { return items[index]; }

    public T this[int index]
    {
        get { return items[index]; }
        set { items[index] = value; }
    }

    IEnumerator<T> IEnumerable<T>.GetEnumerator()
    {
        throw new System.NotImplementedException();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        throw new System.NotImplementedException();
    }
}

public class Exercise
{
    public static void Main()
    {
        Collection<string> names = new Collection<string>();

        names.Add("Bamako");
        names.Add("San Francisco");
        names.Add("Minsk");
        names.Add("Tokyo");
        names.Add("La Paz");

        WriteLine("Cities");
        WriteLine("-------------------");

        int i = 0;
        while(i < names.Count)
        {
            WriteLine("City: " + names[i]);
            i++;
        }

        WriteLine("================================");
        return;
    }
}

On the other hand, the lone member of the System.Collections.Generic.IEnumerable<> interface, the GetEnumerator method, makes it possible for a class that implements that interface to use the foreach loop on the items of a collection. This can be taken care of as follows:

using System.Collections;
using static System.Console;
using System.Collections.Generic;

public class Collection<T> : IEnumerable<T>
{
    T[] items;
    int size;

    public Collection()
    {
        size = 0;
        items = new T[5];
    }

    public virtual int Count => size;
    }

    public void Add(T item)
    {
        items[size] = item;
        size++;
    }

    public T Get(int index) => items[index];

    public T this[int index]
    {
        get { return items[index]; }
        set { items[index] = value; }
    }

    IEnumerator<T> IEnumerable<T>.GetEnumerator()
    {
        int counter = 0;

        while (counter < size)
        {
            yield return items[counter];
            counter++;
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        int counter = 0;

        while (counter < size)
        {
            yield return items[counter];
            counter++;
        }
    }
}

public class Exercise
{
    public static void Main()
    {
        Collection<string> names = new Collection<string>();
        names.Add("Bamako");
        names.Add("San Francisco");
        names.Add("Minsk");
        names.Add("Tokyo");
        names.Add("La Paz");

        WriteLine("Cities");
        WriteLine("-------------------");

        foreach(string city in names)
            WriteLine("City: " + city);
        WriteLine("---------------------");
        WriteLine("Collection Size: " + names.Count);

        WriteLine("================================");
        return;
    }
}

Previous Copyright © 2008-2019, FunctionX Next