Home
C# Language C# Language
C# Language Contents C# Language C# Language C# Language

C# with .NET

Prev Page Next PageNext C# Language
C# Language   C# Language
C# Language Table of Contents
C# Language Back Cover
C# Language Professional C# 2009 with .NET 3.0
C# Language Introduction
C# Language Looking at What’s New in the .NET Framework 2.0
C# Language Introducing the .NET Framework 3.0
C# Language Where C# Fits In
C# Language What You Need to Write and Run C# Code
C# Language What This site Covers
C# Language Conventions
C# Language Source Code
C# Language Errata
C# Language roque-patrick.com
C# Language The C# Language
C# Language .NET Architecture
C# Language The Relationship of C# to .NET
C# Language The Common Language Runtime
C# Language A Closer Look at Intermediate Language
C# Language Assemblies
C# Language .NET Framework Classes
C# Language Namespaces
C# Language Creating .NET Applications Using C#
C# Language The Role of C# in the .NET Enterprise Architecture
C# Language Summary
C# Language C# Basics
C# Language Before We Start
C# Language Your First C# Program
C# Language Variables
C# Language Predefined Data Types
C# Language Flow Control
C# Language Enumerations
C# Language Arrays
C# Language Namespaces
C# Language The Main() Method
C# Language More on Compiling C# Files
C# Language Console I/O
C# Language Using Comments
C# Language The C# Preprocessor Directives
C# Language C# Programming Guidelines
C# Language Summary
C# Language Objects and Types
C# Language Classes and Structs
C# Language Class Members
C# Language Structs
C# Language Partial Classes
C# Language Static Classes
C# Language The Object Class
C# Language Summary
C# Language Inheritance
C# Language Implementation Inheritance
C# Language Modifiers
C# Language Interfaces
C# Language Summary
C# Language Arrays
C# Language Simple Arrays
C# Language Multidimensional Arrays
C# Language Jagged Arrays
C# Language Array Class
C# Language Array and Collection Interfaces
C# Language Enumerations
C# Language Summary
C# Language Operators and Casts
C# Language Operators
C# Language Type Safety
C# Language Comparing Objects for Equality
C# Language Operator Overloading
C# Language User-Defined Casts
C# Language Summary
C# Language Delegates and Events
C# Language Delegate Inference
C# Language Anonymous Methods
C# Language Events
C# Language Summary
C# Language Strings and Regular Expressions
C# Language System.String
C# Language Regular Expressions
C# Language Summary
C# Language Generics
C# Language Overview
C# Language Creating Generic Classes
C# Language Generic Classes’ Features
C# Language Generic Interfaces
C# Language Generic Methods
C# Language Generic Delegates
C# Language Other Generic Framework Types
C# Language Summary
C# Language Collections
C# Language Collection Interfaces and Types
C# Language Lists
C# Language Queue
C# Language Stack
C# Language Linked Lists
C# Language Sorted Lists
C# Language Dictionaries
C# Language Dictionary with Multiple Keys
C# Language Bit Arrays
C# Language Performance
C# Language Summary
C# Language Memory Management and Pointers
C# Language Memory Management under the Hood
C# Language Freeing Unmanaged Resources
C# Language Unsafe Code
C# Language Summary
C# Language Reflection
C# Language Custom Attributes
C# Language Reflection
C# Language Summary
C# Language Errors and Exceptions
C# Language Looking into Errors and Exception Handling
C# Language Summary
C# Language Visual Studio
C# Language Visual Studio 2009
C# Language Refactoring
C# Language Visual Studio 2009 for .NET Framework 3.0
C# Language Summary
C# Language Deployment
C# Language Designing for Deployment
C# Language Deployment Options
C# Language Deployment Requirements
C# Language Deploying the .NET Runtime
C# Language Simple Deployment
C# Language Installer Projects
C# Language ClickOnce
C# Language Summary
C# Language Base Class Libraries
C# Language Assemblies
C# Language What Are Assemblies?
C# Language Assembly Structure
C# Language Cross-Language Support
C# Language Global Assembly Cache
C# Language Creating Shared Assemblies
C# Language Configuration
C# Language Summary
C# Language Tracing and Events
C# Language Tracing
C# Language Event Logging
C# Language Performance Monitoring
C# Language Summary
C# Language Threading and Synchronization
C# Language Overview
C# Language Asynchronous Delegates
C# Language The Thread Class
C# Language Thread Pools
C# Language Threading Issues
C# Language Synchronization
C# Language COM Apartments
C# Language Background Worker
C# Language Summary
C# Language .NET Security
C# Language Code Access Security
C# Language Support for Security in the Framework
C# Language Managing Security Policies
C# Language Role-Based Security
C# Language Summary
C# Language Localization
C# Language Namespace System.Globalization
C# Language Resources
C# Language Localization Example Using Visual Studio
C# Language Localization with ASP.NET
C# Language A Custom Resource Reader
C# Language Creating Custom Cultures
C# Language Summary
C# Language Transactions
C# Language Overview
C# Language Database and Classes
C# Language Traditional Transactions
C# Language System.Transactions
C# Language Isolation Level
C# Language Custom Resource Managers
C# Language Transactions with Windows Vista
C# Language Summary
C# Language Windows Services
C# Language What Is a Windows Service?
C# Language Windows Services Architecture
C# Language System.ServiceProcess Namespace
C# Language Creating a Windows Service
C# Language Monitoring and Controlling the Service
C# Language Troubleshooting
C# Language Power Events
C# Language Summary
C# Language COM Interoperability
C# Language .NET and COM
C# Language Marshaling
C# Language Using a COM Component from a .NET Client
C# Language Using a .NET Component from a COM Client
C# Language Platform Invoke
C# Language Summary
C# Language Data
C# Language Manipulating Files and the Registry
C# Language Managing the File System
C# Language Moving, Copying, and Deleting Files
C# Language Reading and Writing to Files
C# Language Reading Drive Information
C# Language File Security
C# Language Reading and Writing to the Registry
C# Language Reading and Writing to Isolated Storage
C# Language Summary
C# Language Data Access with .NET
C# Language ADO.NET Overview
C# Language Using Database Connections
C# Language Commands
C# Language Fast Data Access: The Data Reader
C# Language Managing Data and Relationships: The DataSet Class
C# Language Populating a DataSet
C# Language Persisting DataSet Changes
C# Language Working with ADO.NET
C# Language Summary
C# Language Manipulating XML
C# Language XML Standards Support in .NET
C# Language Introducing the System.Xml Namespace
C# Language Using MSXML in .NET
C# Language Using System.Xml Classes
C# Language Reading and Writing Streamed XML
C# Language Using the DOM in .NET
C# Language Using XPathNavigators
C# Language XML and ADO.NET
C# Language Serializing Objects in XML
C# Language Summary
C# Language .NET Programming with SQL Server 2009
C# Language .NET Runtime Host
C# Language Microsoft.SqlServer.Server
C# Language User-Defined Types
C# Language Stored Procedures
C# Language User-Defined Functions
C# Language Triggers
C# Language XML Data Type
C# Language Summary
C# Language Presentation
C# Language Windows Forms
C# Language Creating a Windows Form Application
C# Language Control Class
C# Language Standard Controls and Components
C# Language Forms
C# Language Summary
C# Language Viewing .NET Data
C# Language The DataGridView Control
C# Language DataGridView Class Hierarchy
C# Language Data Binding
C# Language Visual Studio .NET and Data Access
C# Language Summary
C# Language Graphics with GDI+
C# Language Understanding Drawing Principles
C# Language Measuring Coordinates and Areas
C# Language A Note about Debugging
C# Language Drawing Scrollable Windows
C# Language World, Page, and Device Coordinates
C# Language Colors
C# Language The Safety Palette
C# Language Pens and Brushes
C# Language Drawing Shapes and Lines
C# Language Displaying Images
C# Language Issues When Manipulating Images
C# Language Drawing Text
C# Language Simple Text Example
C# Language Fonts and Font Families
C# Language Example: Enumerating Font Families
C# Language Editing a Text Document: The CapsEditor Sample
C# Language Printing
C# Language Summary
C# Language Windows Presentation Foundation
C# Language Overview
C# Language Shapes
C# Language Controls
C# Language Layout
C# Language Event Handling
C# Language Commands
C# Language Styles, Templates, and Resources
C# Language Styles
C# Language Animations
C# Language Data Binding
C# Language Windows Forms Integration
C# Language Summary
C# Language ASP.NET Pages
C# Language ASP.NET Introduction
C# Language ASP.NET Web Forms
C# Language ADO.NET and Data Binding
C# Language Application Configuration
C# Language Summary
C# Language ASP.NET Development
C# Language Custom Controls
C# Language Master Pages
C# Language Site Navigation
C# Language Security
C# Language Themes
C# Language Web Parts
C# Language Summary
C# Language ASP.NET AJAX
C# Language What Is Ajax?
C# Language What Is ASP.NET AJAX?
C# Language ASP.NET AJAX-Enabled Web Sites
C# Language Summary
C# Language Communication
C# Language Accessing the Internet
C# Language The WebClient Class
C# Language WebRequest and WebResponse Classes
C# Language Displaying Output as an HTML Page
C# Language Utility Classes
C# Language Lower-Level Protocols
C# Language Summary
C# Language Web Services with ASP.NET
C# Language SOAP
C# Language WSDL
C# Language Web Services
C# Language Extending the Event-siteing Example
C# Language Exchanging Data Using SOAP Headers
C# Language Summary
C# Language .NET Remoting
C# Language What Is .NET Remoting?
C# Language .NET Remoting Overview
C# Language Contexts
C# Language Remote Objects, Clients, and Servers
C# Language .NET Remoting Architecture
C# Language Miscellaneous .NET Remoting Features
C# Language Summary
C# Language Enterprise Services
C# Language Overview
C# Language Creating a Simple COM+ Application
C# Language Deployment
C# Language Component Services Explorer
C# Language Client Application
C# Language Transactions
C# Language Sample Application
C# Language Integrating WCF and Enterprise Services
C# Language Summary
C# Language Message Queuing
C# Language Overview
C# Language Message Queuing Products
C# Language Message Queuing Architecture
C# Language Message Queuing Administrative Tools
C# Language Programming Message Queuing
C# Language Course Order Application
C# Language Receiving Results
C# Language Transactional Queues
C# Language Message Queue Installation
C# Language Summary
C# Language Windows Communication Foundation
C# Language Overview
C# Language Simple Service and Client
C# Language Contracts
C# Language Service Implementation
C# Language Binding
C# Language Hosting
C# Language Clients
C# Language Duplex Communication
C# Language Summary
C# Language Windows Workflow Foundation
C# Language Activities
C# Language Custom Activities
C# Language Workflows
C# Language The Workflow Runtime
C# Language Workflow Services
C# Language Hosting Workflows
C# Language The Workflow Designer
C# Language Summary
C# Language Download Details
C# Language Directory Services
C# Language The Architecture of Active Directory
C# Language Administration Tools for Active Directory
C# Language Programming Active Directory
C# Language Searching for User Objects
C# Language DSML
C# Language Summary
C# Language Part VII: Additional Information
C# Language C#, Visual Basic, and C++/CLI
C# Language Namespaces
C# Language Defining Types
C# Language Methods
C# Language Static Members
C# Language Arrays
C# Language Control Statements
C# Language Loops
C# Language Exception Handling
C# Language Inheritance
C# Language Resource Management
C# Language Delegates
C# Language Events
C# Language Generics
C# Language C++/CLI Mixing Native and Managed Code
C# Language Summary
C# Language Windows Vista
C# Language Vista Bridge
C# Language User Account Control
C# Language Directory Structure
C# Language New Controls and Dialogs
C# Language Search
C# Language Summary
C# Language Language Integrated Query
C# Language Traditional Queries
C# Language LINQ Query
C# Language Query Expressions
C# Language Extension Methods
C# Language Standard Query Operators
C# Language Lambda Expressions
C# Language Deferred Query Execution
C# Language Expression Trees
C# Language Type Inference
C# Language Object and Collection Initializers
C# Language Anonymous Types
C# Language Summary
C# Language Index
C# Language A
C# Language B
C# Language C
C# Language D
C# Language E
C# Language F
C# Language G
C# Language H
C# Language I
C# Language J
C# Language K
C# Language L
C# Language M
C# Language N
C# Language O
C# Language P
C# Language Q
C# Language R
C# Language S
C# Language T
C# Language U
C# Language V
C# Language W
C# Language X
C# Language Y
C# Language Z
C# Language
C# Language
Previous PagePrevious
Next PageNext

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.


Previous PagePrevious
Next PageNext