ASP FAQ Tutorials 8000XXXX Errors
ASP.NET 2.0 Basic Language Constructs Themes & Skins (web) Tips & Techniques XML Serialization
Classic ASP 1.0
Databases
General Concepts
Search Engine Optimization (SEO)
| ASP FAQ Tutorials :: ASP.NET 2.0 :: Basic Language Constructs :: Using Hashtables
Using Hashtables
Hashtables have got to be the greatest thing since sliced bread. Especially for VB programmers, they also are surpisingly under-utilized. Every programmer should learn how to effectively use hashtables as they will likely prove useful in a wide variety of situations.
Hashtables are an extremely effecient way of storing key/value pairs. The key can be an integer (similar to an array), but because of how Hashtables work, the key can actually be anything from a string to an object.
Let's assume for example that you had a class that represented a list of people, and you wanted to be able to access that list of people based on their social security number. You could store them in an array (or array list) and then iterate over that list each time you wanted to access on of those people. The two most pressing problems are that this type of access is both cumbersome and inefficient.
A common use of hashtables is to collect other classes. For example, you might have a class the represents a person, and another class that represents a collection of people. Hashtables can be used to reference the specific people, to allow you to very quickly access specific people based criteria like name or Social Security Number.
Let's say that you had a class that represents a person. This class has two public member variables (name & ssn (social security number)) as follows:
CSharp
public class Person
{
public Person(string name, string ssn)
{
this.Name = name;
this.SSN = ssn;
}
public string Name;
public string SSN;
}VB
Public Class Person
Public Sub New(ByVal name As String, ByVal ssn As String)
Me.Name = name
Me.SSN = ssn
End Sub
Public Name As String
Public SSN As String
End Class
| We could create a list of "people" by just creating an array of person as follows:
We could also create a new class called people as follows. In order to allow multiple people to be managed, we need a public method called Add that would take a Person object as a parameter. We can then use an array list to store the actual person.
CSharp
public class People // Could also be called PersonCollection
{
public People()
{
People = new ArrayList();
}
public ArrayList People;
public void Add(Person person) {
People.Add(person);
}
}VB
Public Class People ' Could also be called PersonCollection
Private Function People() As Public
People = New ArrayList()
End Function
Public People As ArrayList
Public Sub Add(ByVal person As Person)
People.Add(person)
End Sub
End Class
| Now, we can create objects of type person, and add them to the collection of people as follows:
CSharp
// Create the people collection
People people = new People();
// Create a person and add them to the collection
Person ej = new Person("EJ Alexandra", "775-11-1234");
people.Add(ej)
// Create another person and add them
Person peter = new Person("Peter Jackson", "123-45-6789");
people.Add(peter);VB
' Create the people collection
Dim people As People = New People()
' Create a person and add them to the collection
Dim ej As Person = New Person("EJ Alexandra","775-11-1234")
people.Add(ej)
' Create another person and add them
Dim peter As Person = New Person("Peter Jackson","123-45-6789")
people.Add(peter)
| The problem is that without iterating over all of the people in the collection, how do you find 'ej' or the person with the social security number '775-11-1234'. This is where hashtables really start to shine. Let's say that we not only used an array list to keep track of the people, but also used a Hashtable, who's key was based on name and ssn. The "People" class would look like this:
CSharp
public class People // Could also be called PersonCollection
{
public People()
{
m_byName = new Hashtable();
m_bySSN = new Hashtable();
m_byIndex = new ArrayList();
}
private Hashtable m_byName;
private Hashtable m_bySSN;
private ArrayList m_byIndex;
public Person ByName(string name)
{
return (Person)m_byName[name];
}
public Person BySS(string ssn)
{
return (Person)m_bySSN[ssn];
}
public void Add(Person person)
{
m_byName.Add(person.Name, person);
m_bySSN.Add(person.SSN, person);
m_byIndex.Add(person);
}
public void Add(string name, string ssn)
{
Person person = new Person(name, ssn);
Add(person);
}
}VB
Public Class People ' Could also be called PersonCollection
Private Function People() As Public
m_byName = New Hashtable()
m_bySSN = New Hashtable()
m_byIndex = New ArrayList()
End Function
Private m_byName As Hashtable
Private m_bySSN As Hashtable
Private m_byIndex As ArrayList
Public Function ByName(ByVal name As String) As Person
Return CType(m_byName(name), Person)
End Function
Public Function BySS(ByVal ssn As String) As Person
Return CType(m_bySSN(ssn), Person)
End Function
Public Sub Add(ByVal person As Person)
m_byName.Add(person.Name, person)
m_bySSN.Add(person.SSN, person)
m_byIndex.Add(person)
End Sub
Public Sub Add(ByVal name As String, ByVal ssn As String)
Dim person As Person = New Person(name,ssn)
Add(person)
End Sub
End Class
| Notice that in addition to the ArrayList that we had before, we also now have 2 hashtables. m_byName and m_bySSN. When a new person is added, it adds them to the array list, and to the two hashtables. The 1st parameter is the key (Name or SSN), and the 2nd parameter is the object that is being stored. This let's us also add 2 acces methods ByName and BySSN. By just passing in a name or SSN, these access methods can very easily return the associated Person, by just passing that key into the appropriate Hashtable. The code is very simple, easy to undersand and very efficient on the back end of things. The final step that makes this class even more helpful, is to make the People class inherit from the IEnumerable class. This will allow us to interate over the collection of people, just like any other collection as follows:
CSharp
// We can iterate over each Person in the People collection as follows:
foreach (Person person in people) {
// Print person to the screen
Response.Write(person.Name + " - " + person.SSN + "<br>");
}VB
' We can iterate over each Person in the People collection as follows:
Dim person As Person
For Each person In people
' Print person to the screen
Response.Write(person.Name + " - " + person.SSN + "<br>")
Next
| For more information about foreach loops, see this article. Using for and foreach with arrays, array...In order for this to work, we need to inherit from the interface IEnumerable and provide a GetEnumerator() method. people.Count returns how many people have been added. The full people class would look like this:
CSharp
// We can iterate over each Person in the People collection as follows:
public class People : IEnumerable // Could also be called PersonCollection
{
public People()
{
m_byName = new Hashtable();
m_bySSN = new Hashtable();
m_byIndex = new ArrayList();
}
private Hashtable m_byName;
private Hashtable m_bySSN;
private ArrayList m_byIndex;
public Person ByName(string name)
{
return (Person)m_byName[name];
}
public Person BySS(string ssn)
{
return (Person)m_bySSN[ssn];
}
public Person this[int index]
{
get
{
return (Person)m_byIndex[index];
}
}
public void Add(Person person)
{
m_byName.Add(person.Name, person);
m_bySSN.Add(person.SSN, person);
m_byIndex.Add(person);
}
public void Add(string name, string ssn)
{
Person person = new Person(name, ssn);
Add(person);
}
public int Count()
{
return m_byIndex.Count;
}
public IEnumerator GetEnumerator()
{
return m_byIndex.GetEnumerator();
}
}VB
' We can iterate over each Person in the People collection as follows:
Public Class People
Implements IEnumerable ' Could also be called PersonCollection
Public Sub New()
m_byName = New Hashtable()
m_bySSN = New Hashtable()
m_byIndex = New ArrayList()
End Sub
Private m_byName As Hashtable
Private m_bySSN As Hashtable
Private m_byIndex As ArrayList
Public Function ByName(ByVal name As String) As Person
Return CType(m_byName(name), Person)
End Function
Public Function BySS(ByVal ssn As String) As Person
Return CType(m_bySSN(ssn), Person)
End Function
Default Public ReadOnly Property Item(index As Integer) As Person
Get
Return CType(m_byIndex(index), Person)
End Get
End Property
Public Sub Add(ByVal person As Person)
m_byName.Add(person.Name, person)
m_bySSN.Add(person.SSN, person)
m_byIndex.Add(person)
End Sub
Public Sub Add(ByVal name As String, ByVal ssn As String)
Dim person As Person = New Person(name,ssn)
Add(person)
End Sub
Public Function Count() As Integer
Return m_byIndex.Count
End Function
Public Function GetEnumerator() As IEnumerator
Return m_byIndex.GetEnumerator()
End Function
End Class
| This web page provides two textboxes (TextBox1 and TextBox2) that add pesons's to a collection of people which is stored in the Session object. On pre-render, the collection of people is printed in a listbox. default.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
Name:<br />
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox><br />
SSN:
<br />
<asp:TextBox ID="TextBox2" runat="server"></asp:TextBox>
<br />
<asp:Button ID="Button1" runat="server" Text="Add Person" OnClick="Button1_Click" /><br />
<br />
<br />
People:<br />
<asp:ListBox ID="ListBox1" runat="server" Height="221px"
Width="169px"></asp:ListBox></div>
</form>
</body>
</html> | default.aspx.cs/vb
CSharp
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
public partial class _Default : System.Web.UI.Page
{
private People people;
protected void Page_Load(object sender, EventArgs e)
{
if (Session["people"] == null) Session["people"] = new People();
people = (People)Session["people"];
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
ListBox1.Items.Clear();
foreach (Person person in people)
{
ListBox1.Items.Add(String.Format("{0} - {1}",
person.Name, person.SSN));
}
}
protected void Button1_Click(object sender, EventArgs e)
{
people.Add(TextBox1.Text, TextBox2.Text);
}
}VB
Imports System
Imports System.Data
Imports System.Configuration
Imports System.Web
Imports System.Web.Security
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports System.Web.UI.WebControls.WebParts
Imports System.Web.UI.HtmlControls
Public partial Class _Default
Inherits System.Web.UI.Page
Private people As People
Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs)
If Session("people") Is Nothing Then
Session("people") = New People()
End If
people = CType(Session("people"), People)
End Sub
Protected Overrides Sub OnPreRender(ByVal e As EventArgs)
MyBase.OnPreRender(e)
ListBox1.Items.Clear()
Dim person As Person
For Each person In people
ListBox1.Items.Add(String.Format("{0} - {1}",
person.Name, person.SSN))
Next
End Sub
Protected Sub Button1_Click(ByVal sender As Object, ByVal e As EventArgs)
people.Add(TextBox1.Text, TextBox2.Text)
End Sub
End Class
| This code does not do any error checking. Some specific things that would need to be checked have to do with how Hashtables handle duplicate keys. Specifically, they are not accepted. What this means is that before adding a key/value pair to a hashtable, you should really check that they key does not already exist. If you do no, you will likely get an error like the following.
Server Error in '/WebSite1' Application.
Item has already been added. Key in dictionary: 'xxx' Key being added: 'xxx'
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.ArgumentException: Item has already been added. Key in dictionary: 'xxx' Key being added: 'xx'
The way to avoid this error is to check that they key does not exist before adding it as follows:
CSharp
public void Add(Person person)
{
// Check that the name is not already used as a key, add if so
if (!m_byName.ContainsKey(person.Name)) m_byName.Add(person.Name, person);
// Check that the key is not already used as a key, add if so
if (!m_bySSN.ContainsKey(person.SSN)) m_bySSN.Add(person.SSN, person);
// Always add the person to the array list...
m_byIndex.Add(person);
}VB
Public Sub Add(ByVal person As Person)
' Check that the name is not already used as a key, add if so
If Not m_byName.ContainsKey(person.Name) Then
m_byName.Add(person.Name, person)
End If
' Check that the key is not already used as a key, add if so
If Not m_bySSN.ContainsKey(person.SSN) Then
m_bySSN.Add(person.SSN, person)
End If
' Always add the person to the array list...
m_byIndex.Add(person)
End Sub
| Also, the reason that I always include an array list in addition to any Hashtables that I use is that order is not preserved with a hashtable. In other words, I could add keys 1, 2 and 3 and if I access the hashtable by index, it might return 3, 1 and 2. The array list on the other hand will always return the elements in the order in which they were added. This can be important if trying to preserve a specific order.
In closing, I think you can see that hashtables are extraordinarly powerful. They should be used where appropriate and will make a powerful tool in your toolbox under the right circumstances. Good luck.
'---------------------------------------------------------------- ' Converted from C# to VB .NET using CSharpToVBConverter(1.2). ' Developed by: Kamal Patel (http://www.KamalPatel.net) '---------------------------------------------------------------- Related Articles Using for and foreach with arrays, arraylists and other collections
|