Saturday 18 February 2017

Class Indexer

we're going to take a slight break from interfaces and talk about a class indexer, now an indexer is used for custom collections to let you access the elements by their index. to get started let's begin with our standard "Person" class.

class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public override string ToString() { return $"{FirstName} {LastName}"; }

}

nothing special here, next let's create a People class to contain a group of persons, hence the name "People" makes a tad bit of sense doesn't it.

class People
{
    Person[] _ppl;
    public People(params Person[] ppl)
    {
        _ppl = ppl;
    }

    public Person this[int Index]
    {
        get { return _ppl[Index]; }
        set { _ppl[Index] = value; }
    }

}

There's two things worth pointing out here, firstly the constructor, it takes in an array of Persons, but the params keyword let's us pass them in using a comma separated input; you'll see in the implementation of the main class. Secondly the public Person this[int Index] property, this lets us declare a property with no name but let's the compiler know that we can refrence the instance of our People class with an index like so <instanceName>[index] to retrieve our person.

let's take a look at our implementation

class Program
{
    static void Main(string[] args)
    {
        var p1 = new Person { FirstName = "Pawel", LastName = "Ciucias" };
        var p2 = new Person { FirstName = "Tomek", LastName = "Ciucias" };
        var p3 = new Person { FirstName = "Jakub", LastName = "Tywoniuk" };
        var p4 = new Person { FirstName = "Magda", LastName = "Tywoniuk" };

        //becasue of the params key word
        var ppl = new People(p1, p2, p3, p4);

        //because of the indexer
        Console.WriteLine(ppl[3]);
    }

}

now we can see that when we instantiate an instance of our People class we can pass in our values with a comma separated syntax because of the "params" keyword in the constructor signature and we can refer to our collection items with the indexer by using "ppl[3]".

now if we wanted to use a for loop over our People collection, we wouldn't be able to since we don't expose a length method, we could try using a while loop, but eventually we'd get a index out of range exception; instead let's just implement a length property.

class People
{
    Person[] _ppl;
    public People(params Person[] ppl)
    {
        _ppl = ppl;
    }

    public Person this[int Index]
    {
        get { return _ppl[Index]; }
        set { _ppl[Index] = value; }
    }

    public int Length {
        get { return _ppl == null ? 0 : _ppl.Length; }
    }

}

and with just a small change like that, we can now use a for loop to output the contents of our People class.

class Program
{
    static void Main(string[] args)
    {
        var p1 = new Person { FirstName = "Pawel", LastName = "Ciucias" };
        var p2 = new Person { FirstName = "Tomek", LastName = "Ciucias" };
        var p3 = new Person { FirstName = "Jakub", LastName = "Tywoniuk" };
        var p4 = new Person { FirstName = "Magda", LastName = "Tywoniuk" };

        //becasue of the params key word
        var ppl = new People(p1, p2, p3, p4);

        for(var i = 0; i < ppl.Length; i++)
            Console.WriteLine(ppl[i].ToString());
    }

}

next we're going to look at using a foreach loop with our custom collection, but that's it's own post about interfaces.