Lists


For dynamic lists, the .NET Framework offers the classes ArrayList and List<T>. The class List<T> in the namespace System.Collections.Generic is a very similar in its usage to the ArrayList class from the namespace System.Collections. This class implements the IList, ICollection, and IEnumerable interfaces. Because Chapter 9, “Generics,” already discussed the methods of these interfaces, this section looks at how to use the List<T> class.

The following examples use the members of the class Racer as elements to be added to the collection to represent a Formula-1 racer. This class has three fields: firstname, lastname, and the number of wins. The fields can be accessed with properties. With the constructor of the class, the name of the racer and the number of wins can be passed to set the members. The method ToString() is overridden to return the name of the racer. The class Racer also implements the generic interface IComparer<T> for sorting racer elements.

  [Serializable] public class Racer : IComparable<Racer>, IFormattable {    public Racer()       : this(String.Empty, String.Empty, String,Empty) {}    public Racer(string firstname, string lastname, string country)       : this(firstname, lastname, country, 0) {}    public Racer(string firstname, string lastname, string country, int wins)    {       this.firstname = firstname;       this.lastname = lastname;       this.country = country;       this.wins = wins;    }    private string firstname;    public string Firstname    {       get { return firstname; }       set { firstname = value; }    }    private string lastname;    public string Lastname    {       get { return lastname; }       set { lastname = value; }    }    private string country;    public string Country    {       get { return country; }       set { country = value; }    }        private int wins;    public int Wins    {       get { return wins; }        set { wins = value; }    }    public override string ToString()    {       return firstname + " " + lastname;    }    public string ToString(string format, IFormatProvider formatProvider)    {       switch (format)       {          case null:          case "N": // Name             return ToString();          case "F": // Firstname             return firstname;          case "L": // Lastname             return lastname;          case "W": // Wins             return ToString() + " Wins: " + wins;          case "C": // Country             return ToString() + " Country: " + country;          case "A": // All             return ToString() + ", " + country + " Wins:" + wins;          default:             throw new FormatException(String.Format(formatProvider,                   "Format {0} is not supported", format));       }    }    public string ToString(string format)    {       return ToString(format, null);    }    public int CompareTo(Racer other)    {       return this.lastname.CompareTo(other.lastname);    } } 

Creating Lists

You can create list objects by invoking the default constructor. With the generic class List<T>, you must specify the type for the values of the list with the declaration. The code shows how to declare a List<T> with int and a list with Racer elements. ArrayList is a nongeneric list that accepts any Object type for its elements.

Using the default constructor creates an empty list. As soon as elements are added to the list, the capacity of the list is extended to allow 4 elements. If the 5th element is added, the list is resized for a place of 8 elements. If 8 elements are not enough, the list is resized again to contain 16 elements. With every resize the capacity of the list is doubled.

  ArrayList objectList = new ArrayList(); List<int> intList = new List<int>(); List<Racer> racers = new List<Racer>(); 

If the capacity of the list changes the complete collection is reallocated to a new memory block. With the implementation of List<T>, an array of type T is used. With reallocation a new array is created, and Array.Copy() copies the elements from the old to the new array. To save time, if you know the number of elements in advance, that should be in the list; you can define the capacity with the constructor. Here a collection with a capacity of 10 elements is created. If the capacity is not large enough for the elements added, the capacity is resized to 20 and 40 elements - doubled again.

  ArrayList objectList = new ArrayList(10); List<int> intList = new List<int>(10); 

You can get and set the capacity of a collection by using the Capacity property:

  objectList.Capacity = 20; intList.Capacity = 20; 

The capacity is not the same as the number of elements in the collection. The number of elements in the collection can be read with the Count property. Of course, the capacity is always larger or equal to the number of items. As long as no element was added to the list, the count is 0.

  Console.WriteLine(intList.Count); 

If you are finished adding elements to the list and don’t want to add any more elements, you can get rid of the unneeded capacity by invoking the TrimExcess() method. However, because the relocation takes time, TrimExcess() does nothing if the item count is more than 90 percent of capacity.

  intList.TrimExcess(); 

Tip 

Because with new applications usually you can use the generic List<T> class instead of the non-generic ArrayList class, and also because the methods of ArrayList are very similar, the reminder of this section just focuses on List<T>.

Adding Elements

You can add elements to the list with the Add() method as shown. The generic instantiated type defines the type of the first parameter with the Add() method.

  List<int> intList = new List<int>(); intList.Add(1); intList.Add(2); List<string> stringList = new List<string>(); stringList.Add("one"); stringList.Add("two"); 

The variable racers is defined as type List<Racer>. With the new operator a new object of the same type is created. Because the class List<T> was instantiated with the concrete class Racer, now only Racer objects can be added with the Add() method. In the following sample code, four Formula-1 racers are created and added to the collection:

  List<Racer> racers = new List<Racer>(20); Racer graham = new Racer("Graham", "Hill", "UK", 14); racers.Add(graham); Racer emerson = new Racer("Emerson", "Fittipaldi", "Brazil", 14); racers.Add(emerson); Racer mario = new Racer("Mario", "Andretti", "USA", 12); racers.Add(Mario); racers.Add(new Racer("Michael", "Schumacher", "Germany", 91)); racers.Add(new Racer("Mika", "Hakkinen", "Finland", 20)); 

With the AddRange() method of the List<T> class, you can add multiple elements to the collection at once. The method AddRange() accepts an object of type IEnumerable<T>, so you can also pass an array as shown:

  racers.AddRange(new Racer[] {       new Racer("Niki", "Lauda", "Austria", 25),       new Racer("Alain", "Prost", "France", 51)}); 

If you know the elements of the collection when instantiating the list, you can also pass any object that implements IEnumerable<T> to the constructor of the class. This is very similar to the AddRange() method.

  List<Racer> racers = new List<Racer>(new Racer[] {       new Racer("Jochen", "Rindt", "Austria", 6),       new Racer("Ayrton", "Senna", "Brazil", 41) }); 

Inserting Elements

You can insert elements at a specified position with the Insert() method:

  racers.Insert(3, new Racer("Phil", "Hill", "USA", 3)); 

The method InsertRange() offers the capability to insert a number of elements, similarly to the AddRange() method shown earlier.

If the index set is larger than the number of elements in the collection, an exception of type ArgumentOutOfRangeException is thrown.

Accessing Elements

All classes that implement the IList and IList<T> interface offer an indexer, so you can access the elements by using an indexer and passing the item number. The first item can be accessed with an index value 0.

  Racer r1 = racers[3]; 

Getting the number of elements with the Count property, you can do a for loop to iterate through every item in the collection, and use the indexer to access every item:

  for (int i = 0; i < racers.Count; i++) {    Console.WriteLine(racers[i]); } 

Important 

Indexed access to collection classes is available with ArrayList, StringCollection, and List<T>.

Because List<T> implements the interface IEnumerable, you can iterate through the items in the collection using the foreach statement as well.

Tip 

How the foreach statement is resolved by the compiler to make use of the IEnumerable and IEnumerator interfaces is explained in Chapter 5, “Arrays.”

  foreach (Racer r in racers) {    Console.WriteLine(r); } 

Instead of using the foreach statement, the List<T> class also offers a ForEach() method that is declared with an Action<T> parameter. ForEach() iterates through every item of the collection and invokes the method that is passed as parameter for every item.

 public void ForEach(Action<T> action);

For passing a method with ForEach, Action<T> is declared as a delegate that defines a method with void return type and parameter T.

 public delegate void Action<T>(T obj);

With a list of Racer items, the handler for the ForEach() method must be declared with a Racer object as parameter and void return type.

 public void ActionHandler(Racer obj);

Because one overload of the Console.WriteLine() method accepts Object as parameter, you can pass the address of this method to the ForEach method, and every racer of the collection is written to the console:

  racers.ForEach(Console.WriteLine); 

You can also write an anonymous method that accepts a Racer object as parameter. Here, the format A is used with the ToString() method of the IFormattable interface to display all information of the racer.

  racers.ForEach(    delegate(Racer r)    {    Console.WriteLine("{0:A}", r); }); 

Tip 

Anonymous methods are explained in Chapter 7, “Delegates and Events.”

Removing Elements

You can remove elements by index or pass the item that should be removed. Here, the fourth element is removed by passing 3 to RemoveAt():

  racers.RemoveAt(3); 

You can also directly pass a Racer object to the Remove() method to remove this element. Removing by index is faster, as here the collection must be searched for the item to remove. The Remove() method first searches in the collection to get the index of the item with the IndexOf() method and then uses the index to remove the item. IndexOf() first checks if the item type implements the interface IEquatable. If it does, the Equals() method of this interface is invoked to find the item in the collection that is the same as the one passed to the method. If this interface is not implemented, the Equals() method of the Object class is used to compare the items. The default implementation of the Equals() method in the Object class does a bitwise compare with value types, but compares only references with reference types.

Tip 

Chapter 6, “Operators and Casts,” explains how you can override the Equals() method.

Here, the racer referenced by the variable graham is removed from the collection. The variable graham was created earlier when the collection was filled. Because the interface IEquatable and the Object.Equals() method are not overridden with the Racer class, you cannot create a new object with the same content as the item that should be removed and pass it to the Remove() method.

  if (!racers.Remove(graham)) {    Console.WriteLine("object not found in collection"); } 

The method RemoveRange() removes a number of items from the collection. The first parameter specifies the index where the removal of items should begin; the second parameter specifies the number of items to be removed.

  int index = 3; int count = 5; racers.RemoveRange(index, count); 

To remove all items with some specific characteristics from the collection, you can use the RemoveAll() method. This method uses the Predicate<T> parameter that is discussed next when searching for elements. For removing all elements from the collection, use the Clear() method defined with the ICollection<T> interface.

Searching

There are different ways to search for elements in the collection. You can get the index to the found item, or the item itself. You can use methods such as IndexOf(), LastIndexOf(), FindIndex(), FindLastIndex(), Find(), and FindLast(). And for just checking if an item exists, the List<T> class offers the Exists() method.

The method IndexOf() requires an object as parameter and returns the index of the item if it is found inside the collection. If the item is not found, -1 is returned. Remember that IndexOf() is using the IEquatable interface for comparing the elements.

  int index1 = racers.IndexOf(mario); 

With the IndexOf() method, you can also specify that the complete collection should not be searched, but rather specify an index where the search should start and the number of elements that should be iterated for the comparison.

Instead of searching a specific item with the IndexOf() method, you can search for an item that has some specific characteristics that you can define with the FindIndex() method. FindIndex() requires a parameter of type Predicate:

 public int FindIndex(Predicate<T> match);

The Predicate<T> type is a delegate that returns a Boolean value and requires type T as parameter. This delegate can be used similarly to the Action delegate shown earlier with the ForEach() method. If the predicate returns true, there’s a match and the element is found. If it returns false, the element is not found and the search continues.

 public delegate bool Predicate<T>(T obj);

With the List<T> class that is using Racer objects for type T, you can pass the address of a method that returns a bool and defines a parameter of type Racer to the FindIndex() method. Finding the first racer of a specific country, you can create the FindCountry class as shown. The Find() method has the signature and return type defined by the Predicate<T> delegate. The Find() method uses the variable country to search for a country that you can pass with the constructor of the class.

  public class FindCountry {    public FindCountry(string country)    {       this.country = country;    }    private string country;    public bool FindCountryPredicate(Racer r)    {       if (r == 0) throw new ArgumentNullException("r");       return r.Country == country;    } } 

With the FindIndex() method you can create a new instance of the FindCountry() class, pass a country string to the constructor, and pass the address of the Find method. After FindIndex() completes successfully, index2 contains the index of the first item where the Country property of the racer is set to Finland.

  int index2 = racers.FindIndex(new FindCountry("Finland").FindCountryPredicate); 

Instead of creating a class with a handler method, you can create an anonymous method here as well. The result is exactly the same as before. Now the delegate defines the implementation of the anonymous method to search for an item where the Country property is set to Finland.

  int index3 = racers.FindIndex(    delegate(Racer r)    {       return r.Country == "Finland";    }); 

Similarly to the IndexOf() method, with the FindIndex() method you can also specify the index where the search should start and the count of items that should be iterated through. To do a search for an index beginning from the last element in the collection, you can use the FindLastIndex() method.

The method FindIndex() returns the index of the found item. Instead of getting the index, you can also get directly to the item in the collection. The Find() method requires a parameter of type Predicate<T> similarly to the FindIndex() method. The Find() method here is searching for the first racer in the list that has the Firstname property set to Niki. Of course, you can also do a FindLast() to find the last item that fulfills the predicate.

  Racer r = racers.Find(    delegate(Racer r)    {       return r.Firstname == "Niki";    }); 

To get not only one, but all, items that fulfill the requirements of a predicate, you can use the FindAll() method. The FindAll() method uses the same Predicate<T> delegate as the Find() and FindIndex() methods. The FindAll() method just does not stop when the first item is found but instead iterates through every item in the collection and returns all items where the predicate returns true.

With the FindAll() method invoked here, all racer items are returned where the property Wins is set to more than 20. All racers that won more than 20 races are referenced from the bigWinners list.

  List<Racer> bigWinners = racers.FindAll(    delegate(Racer r)    {       return r.Wins > 20;    }); 

Iterating through the variable bigWinners with a foreach statement gives the following result:

        foreach (Racer r in bigWinners)        {              Console.WriteLine("{0:A}", r);        } Michael Schumacher, Germany Wins: 91 Niki Lauda, Austria Wins: 25 Alain Prost, France Wins: 51

The result is not sorted, but this is done next.

Sorting

The List<T> class allows sorting its elements. The Sort() method has several overloads defined. The arguments that can be passed are a generic delegate Comparison<T>, the generic interface IComparer<T>, and a range together with the generic interface IComparer<T>:

 public void List<T>.Sort(); public void List<T>.Sort(Comparison<T>); public void List<T>.Sort(IComparer<T>); public void List<T>.Sort(Int32, Int32, IComparer<T>);

Using the Sort() method without arguments is possible only if the elements in the collection implement the interface IComparable.

The class Racer implements the interface IComparable<T> to sort racers by the last name:

  racers.Sort(); racers.ForEach(Console.WriteLine); 

If you need to do a sort other than the default supported by the item types, you need to use other techniques, for example passing an object that implements the IComparer<T> interface.

The class RacerComparer implements the interface IComparer<T> for Racer types. This class allows you to sort either by the first name, last name, country, or number of wins. The kind of sort that should be done is defined with the inner enumeration type CompareType. The CompareType is set with the constructor of the class RacerComparer. The interface IComparer<Racer> defines the method Compare that is required for sorting. In the implementation of this method, the CompareTo() method of the string and int types is used.

  public class RacerComparer : IComparer<Racer> {    public enum CompareType    {       Firstname,       Lastname,       Country,       Wins    }    private CompareType compareType;    public RacerComparer(CompareType compareType)     {       this.compareType = compareType;    }    public int Compare(Racer x, Racer y)    {       if (x == null) throw new ArgumentNullException("x");       if (y == null) throw new ArgumentNullException("y");       int result;       switch (compareType)       {          case CompareType.Firstname:             return x.Firstname.CompareTo(y.Firstname);          case CompareType.Lastname:             return x.Lastname.CompareTo(y.Lastname);          case CompareType.Country:             if ((result = x.Country.CompareTo(y.Country) == 0)                return x.Lastname.CompareTo(y.Lastname);             else                return res;          case CompareType.Wins:             return x.Wins.CompareTo(y.Wins);          default:             throw new ArgumentException("Invalid Compare Type");       }    } } 

An instance of the RacerComparer class can now be used with the Sort() method. Passing the enumeration RacerComparer.CompareType.Country sorts the collection by the property Country.

  racers.Sort(new RacerComparer(RacerComparer.CompareType.Country)); racers.ForEach(Console.WriteLine); 

Another way to do the sort is by using the overloaded Sort method, which requires a Comparison<T> delegate:

 public void List<T>.Sort(Comparison<T>);

Comparison<T> is a delegate to a method that has two parameters of type T and a return type int. If the parameter values are equal, the method must return 0. If the first parameter is less than the second, a value less than zero must be returned; otherwise, a value greater than zero is returned.

 public delegate int Comparison<T>(T x, T y);

Now you can pass a simple anonymous method to the Sort() method to do a sort by the number of wins. The two parameters are of type Racer, and in the implementation the Wins properties are compared by using the int method CompareTo(). In the implementation, r2 and r1 are used in the reverse order, so the number of wins is sorted in descending order. After the method has been invoked, the complete racer list is sorted based on the name of the racer.

  racers.Sort(delegate(Racer r1, Racer r2) {       return r2.Wins.CompareTo(r1.Wins); }); 

You can also reverse the order of a complete collection by invoking the Reverse() method.

Type Conversion

With the List<T> method ConvertAll(), all types of a collection can be converted to a different type. The ConvertAll() method uses a Converter delegate that is defined like this:

 public sealed delegate TOutput Converter<TInput, TOutput>(TInput from);

The generic types TInput and TOutput are used with the conversion. TInput is the argument of the delegate method, and TOutput is the return type.

In this example, all Racer types should be converted to Person types. Whereas the Racer type contains a name and a car, the Person type just contains a name. For the conversion, the country of the racer can be ignored, but the name must be converted:

  [Serializable] public class Person {    private string name;    public Person(string name)    {       this.name = name;    }    public override string ToString()    {       return name;    } } 

The conversion happens by invoking the racers.ConvertAll<Person>() method. The argument of this method is defined as an anonymous method with an argument of type Racer and a Person type that is returned. In the implementation of the anonymous method, a new Person object is created and returned. For the Person object, the Firstname and Lastname are passed to the constructor:

  List<Person> persons = racers.ConvertAll<Person>(    delegate(Racer r)    {       return new Person(r.Firstname + " " + r.Lastname);    }); 

The result of the conversion is a list containing the converted Person objects: persons of type List<Person>.

Read-Only Collections

After collections are created they are read/write. Of course, they must be read/write; otherwise, you couldn’t fill it with any values. However, after the collection is filled, you can create a read-only collection. The List<T> collection has the method AsReadOnly that returns an object of type ReadOnlyCollection<T>.

The class ReadOnlyCollection<T> implements the same interfaces as List<T>, but all methods and properties that change the collection throw a NotSupportedException.




Professional C# 2005 with .NET 3.0
Professional C# 2005 with .NET 3.0
ISBN: 470124725
EAN: N/A
Year: 2007
Pages: 427

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net