.comment-link {margin-left:.6em;}
My Photo
Name:
Location: Unspecified, Mauritius

I too, am a bug within Māyā.

My Other Blog(s) and Site(s)
Friend sites and blogs
My Recent Posts
Tutorials
Best Articles
My C-Sharp Ideas
Archives
Blog Directories

Thursday, July 21, 2005

 

Indexers

Indexers in C#
Intermediate

I've been up to nothing good on C#, and I thought I'd post something about indexers on C#. Being able to provide a collection class with an indexer could be a real cool thing, if you know how to do it :)

Indexers allow your class(es) to be treated like arrays - an "index" is passed in between square brackets, and an object is returned. Just like an arraylist. Or an array :)

To begin my second C# tutorial, please take a look at the utterly useless Item class:




/// <summary>
/// Just a dummy class named Item, with a few properties.
/// </summary>
public class Item
{
//Name represents the name of the item class
private string name;
//Data represents a data value
private string data;

/// <summary>
/// Constructor for the Item class.
/// </summary>
/// <param name="name">String representing the name of the class</param>
/// <param name="data">Data the class will hold</param>
public Item(string name, string data)
{
this.name = name;
this.data = data;
}

/// <summary>
/// Gets or sets the name of the class
/// </summary>
public string Name
{
get
{
return this.name;
}
set
{
this.name = value;
}
}

/// <summary>
/// Gets or sets the it's data value
/// </summary>
public string Data
{
get
{
return this.data;
}
set
{
this.data = value;
}
}

// - Little Note -
// It's always good to use properties instead of
// directly exposing a variable as public
}



Well, as you can see, the Item class doesn't do anything particular. Of course, it's just a dummy class, created for the purpose of this tutorial. What would interest you would be the ItemHolder class - a class that can actually accept a collection of items. Check out the code below:




/// <summary>
/// A class that can hold Items
/// </summary>
public class ItemHolder
{
//This arraylist will actually contain all the items
private ArrayList itemList;

/// <summary>
/// Creates a new instance of the ItemHolder class, which is able
/// to hold multiple items.
/// </summary>
public ItemHolder()
{
itemList = new ArrayList();
}

/// <summary>
/// Indexer for the ItemHolder class
/// </summary>
public Item this[int pos]
{
get
{
//Check whether the position is within the arraylist's index bounds
if ((pos > this.itemList.Count-1) || (pos < 0))
{
Exception myExcp =
new Exception("Index was out of bounds of array");
throw myExcp;
}
else
{
//typecast to Item, and return
return ((Item)this.itemList[pos]);
}
}

set
{
//Check whether the position is within the arraylist's index bounds
if ((pos > this.itemList.Count-1) || (pos < 0))
{
Exception myExcp =
new Exception("Index was out of bounds of array");
throw myExcp;
}
else
{
//Make a type check, just in case
if (value.GetType() == typeof(Item))
{
Exception myExcp =
new Exception("You can only assign objects of the type \"Item\"");
throw myExcp;
}
else
{
this.itemList[pos] = value;
}
}
}
}

/// <summary>
/// Adds an item to the collection of Items
/// </summary>
/// <param name="someItem"></param>
public void AddItem(Item someItem)
{
//Check whether the item's not null
if (someItem != null)
{
this.itemList.Add(someItem);
}
else
{
Exception myExcp =
new Exception("Null objects are not allowed in this collection");
throw myExcp;
}
}

/// <summary>
/// Returns a count of the total number of items inside
/// the this object
/// </summary>
public int Count
{
get
{
return this.itemList.Count;
}
}
}


Do take a closer look at the indexer:




/// <summary>
/// Indexer for the ItemHolder class
/// </summary>
public Item this[int pos]
{
get
{
//Check whether the position is within the arraylist's index bounds
if ((pos > this.itemList.Count-1) || (pos < 0))
{
Exception myExcp =
new Exception("Index was out of bounds of array");
throw myExcp;
}
else
{
//typecast to Item, and return
return ((Item)this.itemList[pos]);
}
}

set
{
//Check whether the position is within the arraylist's index bounds
if ((pos > this.itemList.Count-1) || (pos < 0))
{
Exception myExcp =
new Exception("Index was out of bounds of array");
throw myExcp;
}
else
{
//Make a type check, just in case
if (value.GetType() == typeof(Item))
{
Exception myExcp =
new Exception("You can only assign objects of the type \"Item\"");
throw myExcp;
}
else
{
this.itemList[pos] = value;
}
}
}
}


This is what we're interested in. Disregard the code - it's just exceptions I couldn't resist throwing just in case the wrong type's being used (I'm forcing the use of Item types of objects inside the collection). The indexer doesn't get more complicated than





public <DataType> this[<DataType> <VariableName>]
{
get
{
}

set
{
}
}


Where the first DataType represents the return type of the indexer, the second DataType the type of the value passed into the square brackets, and VariableName the name of the variable that you want to use for the value that the user has provided. There's also a get and a set property (the accessors) which enable you to execute different code, depending on whether the user's only reading the value, or modifying it.

Now, for the code inside the indexer -

Notice that my indexer directly returns an Item (saves you the typecasting, heh), given an int - which represents the index value of the Item inside of my collection. When the user tries to get an item, the get part of the accessor is executed. I made a little check on the value of the index being passed. Passing -1 would most certainly raise an exception - as well as passing a value outside of the bounds of the collection.

In the set accessor, I had to carry out even more checks - apart from the bounds checking, I also made a little check on the type of value being set. I modified the code a little bit, because using "is" creates more solid code than using GetType() (see below). The is keyword checks for parent objects, and the GetType() only checks for the type. Below is the code that I replaced:





//Make a type check, just in case
if (value is Item)
{
Exception myExcp =
new Exception("You can only assign objects of the type \"Item\"");
throw myExcp;
}
else
{
this.itemList[pos] = value;
}



Instead of:





//Make a type check, just in case
if (value.GetType() == typeof(Item))
{
Exception myExcp =
new Exception("You can only assign objects of the type \"Item\"");
throw myExcp;
}
else
{
this.itemList[pos] = value;
}


The set accessor first checks whether the index passed resides within the collection's bounds (more than -1, less than the count of number of items), and then checks for the type being set; after modification of the code, an object derived from Item will also be accepted in the Indexer.

Note that I also implemented an AddItem method, which allows users to kick in Items into the collection (without which, this whole class would be useless), and a Count property, which allows users to get the number of total items inside of the class. Of course, I could've elaborated, implemented a Delete method and so on, but that's up to you. Below is the code that I used to make the Item and ItemHolder classes work:




[STAThread]
static void Main(string[] args)
{
Item myItem1 = new Item("Rowan",
"Shit happens");

Item myItem2 = new Item("Vidi",
"Life is a bitch, and then you die");

Item myItem3 = new Item("Natasha",
"Good girls go to heaven, Bad girls get what they want");

ItemHolder holder = new ItemHolder();
holder.AddItem(myItem1);
holder.AddItem(myItem2);
holder.AddItem(myItem3);

for (int i=0; i<=holder.Count-1; i++)
{
Console.WriteLine("Name: " + holder[i].Name);
Console.WriteLine("Quote: " + holder[i].Data);
Console.WriteLine();
}

Console.ReadLine();
}


Here's the complete code for the tutorial:



using System;
using System.Collections;

namespace Indexer
{
/// <summary>
/// Comments are pretty useful, even in tutorials
/// </summary>
class IndexerTest
{
/// <summary>
/// Main entry pt. for app.
/// </summary>
[STAThread]
static void Main(string[] args)
{
Item myItem1 = new Item("Rowan",
"Shit happens");

Item myItem2 = new Item("Vidi",
"Life is a bitch, and then you die");

Item myItem3 = new Item("Natasha",
"Good girls go to heaven, Bad girls get what they want");

ItemHolder holder = new ItemHolder();
holder.AddItem(myItem1);
holder.AddItem(myItem2);
holder.AddItem(myItem3);

for (int i=0; i<=holder.Count-1; i++)
{
Console.WriteLine("Name: " + holder[i].Name);
Console.WriteLine("Quote: " + holder[i].Data);
Console.WriteLine();
}

Console.ReadLine();
}
}

/// <summary>
/// Just a dummy class named Item, with a few properties.
/// </summary>
public class Item
{
//Name represents the name of the item class
private string name;
//Data represents a data value
private string data;

/// <summary>
/// Constructor for the Item class.
/// </summary>
/// <param name="name">String representing the name of the class</param>
/// <param name="data">Data the class will hold</param>
public Item(string name, string data)
{
this.name = name;
this.data = data;
}

/// <summary>
/// Gets or sets the name of the class
/// </summary>
public string Name
{
get
{
return this.name;
}
set
{
this.name = value;
}
}

/// <summary>
/// Gets or sets the it's data value
/// </summary>
public string Data
{
get
{
return this.data;
}
set
{
this.data = value;
}
}

// - Little Note -
// It's always good to use properties instead of
// directly exposing a variable as public
}


/// <summary>
/// A class that can hold Items
/// </summary>
public class ItemHolder
{
//This arraylist will actually contain all the items
private ArrayList itemList;

/// <summary>
/// Creates a new instance of the ItemHolder class, which is able
/// to hold multiple items.
/// </summary>
public ItemHolder()
{
itemList = new ArrayList();
}

/// <summary>
/// Indexer for the ItemHolder class
/// </summary>
public Item this[int pos]
{
get
{
//Check whether the position is within the arraylist's index bounds
if ((pos > this.itemList.Count-1) || (pos < 0))
{
Exception myExcp =
new Exception("Index was out of bounds of array");
throw myExcp;
}
else
{
//typecast to Item, and return
return ((Item)this.itemList[pos]);
}
}

set
{
//Check whether the position is within the arraylist's index bounds
if ((pos > this.itemList.Count-1) || (pos < 0))
{
Exception myExcp =
new Exception("Index was out of bounds of array");
throw myExcp;
}
else
{
//Make a type check, just in case
if (value is Item)
{
Exception myExcp =
new Exception("You can only assign objects of the type \"Item\"");
throw myExcp;
}
else
{
this.itemList[pos] = value;
}
}
}
}

/// <summary>
/// Adds an item to the collection of Items
/// </summary>
/// <param name="someItem"></param>
public void AddItem(Item someItem)
{
//Check whether the item's not null
if (someItem != null)
{
this.itemList.Add(someItem);
}
else
{
Exception myExcp =
new Exception("Null objects are not allowed in this collection");
throw myExcp;
}
}

/// <summary>
/// Returns a count of the total number of items inside
/// the this object
/// </summary>
public int Count
{
get
{
return this.itemList.Count;
}
}
}


}