Thursday, January 10, 2008

Using ArrayList and Generic List<> in .NET 2.0 Framework (Part 1)

There are several articles in the web regarding Generics (System.Collections.Generics). Being I use these objects extensively, I thought I would put my two cents and blog on these objects.

The objects we are going to do a quick look at are "System.Collections.ArrayList" and "System.Collections.Generics.List<>". These two objects are very similar in nature, except for the Generics List which is strongly typed objects. Between the two objects, the biggest advantage List object is that it do not require type casting being it is declared with a specific type. This allows us to simply skip the type casting of objects which is required in the "ArrayList". Lets us look at the objects more in detail below.

Lets create a simple class called “MyClass”. We will use this class for our type declarations.

class MyClass
{
public string FirstName;
public string LastName;
public int Age;

public MyClass() { }

public MyClass(string firstName, string lastName, int age)
{
FirstName = firstName;
LastName = lastName;
Age = age;
}
}

ArrayList to create the collection, and see how we could use it normally in code. As you see below, to retrieve the value of the "FirstName" from the instance, we will have to first cast the object and then request the value from the "FirstName" field.

// Init
ArrayList MyList = new ArrayList();

// Add items to collection
MyList.Add(new MyClass("Bob", "Know", 25));
MyList.Add(new MyClass("John", "Doe", 30));
MyList.Add(new MyClass("Bill", "Masters", 25));

// using the object
// retrieve First Name from the second instance of the object
string FirstName = ((MyClass)MyList[1]).FirstName;

With Generics, we can retrieve the value of the "FirstName" from the instance directly. This would avoid any performance issues that may be involved during type casting of the object. Generics also provide the same advantages as the "ArrayList," and several additional advantages that normally requires additional coding if we wish to perform common tasks. Below are several simple tasks using in-built methods.

// Init
List<MyClass> MyClassList = new List<MyClass>();

// Add items to collection
MyClassList.Add(new MyClass("Bob", "Know", 25));
MyClassList.Add(new MyClass("John", "Doe", 30));
MyClassList.Add(new MyClass("Bill", "Masters", 25));

// using the object
// retrieve First Name from the second instance of the object
string FirstName = MyClassList[1].FirstName;


1. Creating a new collection using another collection

This option is available in both "ArrayList" and generic "List". Let us start by creating a simple array of "MyClass" called "MyClassArray".

// Init
MyClass[] MyClassArray = new MyClass[3];

// Add items to collection
MyClassArray[0] = new MyClass("Bob", "Know", 25);
MyClassArray[1] = new MyClass("John", "Doe", 30);
MyClassArray[2] = new MyClass("Bill", "Masters", 25);
System.Collections.ArrayList
// Init
ArrayList MyClassList = new ArrayList(MyClassArray);
System.Collections.Generics.List<>
// Init
List<MyClass> MyClassList = new List<MyClass>(MyClassArray);

As we notice above, we are passing in the "MyClassArray" as a parameter in the object's constructor, which will automatically copy the the items in the array to the "ArrayList" or the generic "List" object. The constructor also has the same option to declare the objects with the capacity by passing in the integer.



2. Using the "AddRange" method

This method allows you to append a collection into an already instantiated object.

System.Collections.ArrayList
// Init
ArrayList MyClassList = new ArrayList();
MyClassList.AddRange(MyClassArray);
System.Collections.Generics.List<>
// Init
List<MyClass> MyClassList = new List<MyClass>();
MyClassList.AddRange(MyClassArray);


3. Using the "BinarySearch" method


This method allows you to find the index of first available item that matches the criteria. In this scenario, I want to compare the FirstName. Below is the simple ICompare class that I will use in my scenario.

// compare class
public class FirstNameCompare : IComparer
{

public int Compare(object x, object y)
{

MyClass S1 = (MyClass)x;
MyClass S2 = (MyClass)y;
return -string.Compare(S1.FirstName, S2.FirstName);

}

}
// create search class
MyClass SearchObject = new MyClass();
SearchObject.FirstName = "Bill";
// create compare
ICompare CompareObject = new FirstNameCompare();

System.Collections.ArrayList
// Init
ArrayList MyClassList = new ArrayList(MyClassArray);
int Index = MyClassList.BinarySearch(SearchObject, CompareObject);
// Index will be 2
System.Collections.Generics.List<>
// Init
List<MyClass> MyClassList = new List<MyClass>(MyClassArray);
int Index = MyClassList.BinarySearch(SearchObject, CompareObject);
// Index will be 2
BinarySearch also provides another overload where you can search using start index, and number of objects. This is useful if one wants to search in a specific range of objects.



4. Using the "Contains" method

"Contains" can only be used if our collection is solemnly based of primitives. When using custom classes, these methods internally use "Equals" methods to perform the comparison, and the HashCode in the objects will always give the wrong output. (If some one can give me a better explanation, I will really appreate it.)



5. Using the "ConvertAll" method

Allows us to convert all items into another type and is only available in a generic List. This provide us several uses, and one such use is displayed in the code.
// Init
List MyClassList = new List<MyClass>(MyClassArray);

// convert
List<String> NameList = MyClassList.ConvertAll(
delegate(MyClass value)
{
return value.FirstName;
}
);

// final Result contain "Bob,John,Bill"
string Result = string.Join(",", NameList.ToArray());

6. Using the "Find" method

Allows us to find the first instance of the object based on a criteria. In the code below, I am going to find first available "Bill" in the "MyClass.FirstName" field in the collection.
// Init
List<MyClass> MyClassList = new List<MyClass>(MyClassArray);

// search text that will be used to perform filter
string SearchText = "Bill";

// find the instance
MyClass MyClassInstance = MyClassList.Find(
delegate(MyClass value)
{
return value.FirstName.Equals(SearchText);
}
);

7. Using the "FindAll" method

Allows us to find all instances of the object that match the criteria. Please note, we should not use any the Object.Equal feature to perform the validation. This will always return "false" value unless you are overriding the "Equal" method and performing custom validation.
// Init
List<MyClass> MyClassList = new List<MyClass>();
// add same collection three times
MyClassList.AddRange(MyClassArray);
MyClassList.AddRange(MyClassArray);
MyClassList.AddRange(MyClassArray);
// search text that will be used to perform filter
string SearchText = "Bill";
// find all instance that contain FirstName of "Bill"
// result will be three instances
List<MyClass> FilteredMyClassCollection = MyClassList.Find(
delegate(MyClass value)
{
return value.FirstName.Equals(SearchText);
}
);

8. Using the "ToArray" method

This method allows us to convert the collection into object array.

System.Collections.ArrayList

// Init
ArrayList MyClassList = new ArrayList(MyClassArray);

// convert to array
MyClass[] MyClassArray = (MyClass[])MyClassList.ToArray(typeof(MyClass));


System.Collections.Generics.List<>
// Init
List<MyClass> MyClassList = new List<MyClass>(MyClassArray);
// convert to array
MyClass[] MyClassArray = MyClassList.ToArray();
As you notice that, type casting is required in converting items in theArrayList collection to an Array. Whereas, the strongly typed generic"List". This is one such location where best judgment needs to be madein choosing the type of collection. Type casting from ArrayList whenthere several items in its collection might become a performance hog.

No comments: