.NET generics

Thu, Oct 26, 2006 3-minute read

I recently started using generics for .NET 2.0 a while ago, and when faced with the problem of searching a custom list for a specific item, I found the following method: Find

public T Find (
    Predicate<T> match
)

The examples in the documentation are a bit lacking since they don't really show the power of what you can do with this method.

Excerpt from the documentation:


The Predicate is a delegate to a method that returns true if the object passed to it matches the conditions defined in the delegate. The elements of the current List are individually passed to the Predicate delegate, moving forward in the List, starting with the first element and ending with the last element. Processing is stopped when a match is found.


All this is fine, but the example that the documentation gives is vague:
using System;
using System.Collections.Generic;

public class Example
{
    public static void Main()
    {
        List<string> dinosaurs = new List<string>();

        dinosaurs.Add("Compsognathus");
        dinosaurs.Add("Amargasaurus");
        dinosaurs.Add("Oviraptor");
        dinosaurs.Add("Velociraptor");
        dinosaurs.Add("Deinonychus");
        dinosaurs.Add("Dilophosaurus");
        dinosaurs.Add("Gallimimus");
        dinosaurs.Add("Triceratops");

        Console.WriteLine();
        foreach (string dinosaur in dinosaurs)
        {
            Console.WriteLine(dinosaur);
        }

        Console.WriteLine("\nTrueForAll(EndsWithSaurus): {0}",
            dinosaurs.TrueForAll(EndsWithSaurus));

        Console.WriteLine("\nFind(EndsWithSaurus): {0}",
            dinosaurs.Find(EndsWithSaurus));

        Console.WriteLine("\nFindLast(EndsWithSaurus): {0}",
            dinosaurs.FindLast(EndsWithSaurus));

        Console.WriteLine("\nFindAll(EndsWithSaurus):");
        List<string> sublist = dinosaurs.FindAll(EndsWithSaurus);

        foreach (string dinosaur in sublist)
        {
            Console.WriteLine(dinosaur);
        }

        Console.WriteLine(
            "\n{0} elements removed by RemoveAll(EndsWithSaurus).",
            dinosaurs.RemoveAll(EndsWithSaurus));

        Console.WriteLine("\nList now contains:");
        foreach (string dinosaur in dinosaurs)
        {
            Console.WriteLine(dinosaur);
        }

        Console.WriteLine("\nExists(EndsWithSaurus): {0}",
            dinosaurs.Exists(EndsWithSaurus));
    }

    // Search predicate returns true if a string ends in "saurus".
    private static bool EndsWithSaurus(String s)
    {
        if ((s.Length > 5) &&
            (s.Substring(s.Length - 6).ToLower() == "saurus"))
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}

/* This code example produces the following output:

Compsognathus
Amargasaurus
Oviraptor
Velociraptor
Deinonychus
Dilophosaurus
Gallimimus
Triceratops

TrueForAll(EndsWithSaurus): False

Find(EndsWithSaurus): Amargasaurus

FindLast(EndsWithSaurus): Dilophosaurus

FindAll(EndsWithSaurus):
Amargasaurus
Dilophosaurus

2 elements removed by RemoveAll(EndsWithSaurus).

List now contains:
Compsognathus
Oviraptor
Velociraptor
Deinonychus
Gallimimus
Triceratops

Exists(EndsWithSaurus): False
*/

An example by me:

using System;
using System.Collections.Generic;
using System.Text;

namespace Generics_test
{
    class Class1
    {
    }

    public class Servant
    {
        string name = "";
        /// <summary>
        /// Gets/Sets the name of the servant
        /// </summary>
        public string Name
        {
            get
            {
                return name;
            }
            set
            {
                name = value;
            }
        }
        int age = 18;
        /// <summary>
        /// Gets/Sets the age of the servant
        /// </summary>
        public int Age
        {
            get
            {
                return age;
            }
            set
            {
                age = value;
            }
        }
        SexFlag sex = SexFlag.Male;
        /// <summary>
        /// Gets/Sets the sex of the servant
        /// </summary>
        public SexFlag Sex
        {
            get
            {
                return sex;
            }
            set
            {
                sex = value;
            }
        }
    }
    /// <summary>
    /// Enum for sex
    /// </summary>
    public enum SexFlag
    {
        Male,
        Female
    }

    public class Master
    {
        List<Servant> servants = new List<Servant>();
        public List<Servant> Servants
        {
            get
            {
                return servants;
            }
        }

    }
}

Okay, consider the fact that you want to find all Servants in an object of type Master with the following properties:

  • Age should be greater then 18, but less than 25
  • The servant should be female
  • your propably know where this is going :-D

One way of doing it in .NET 1.1 was to manually loop through all the Servant objects in the servants list of the Master object.
e.g.

        /// <summary>
        /// Finds all female servants of age between 18 and 25
        /// .NET 1.1 way of doing it.
        /// Lets pretend that the servants List is an ArrayList as well
        /// </summary>
        public ArrayList FindValidCandiates()
        {
            ArrayList valids = new ArrayList();
            foreach (Servant servant in servants)
            {
                if (servant.Sex == SexFlag.Female && servant.Age >= 18 && servant.Age < 26)
                {
                    //Servant is valid, add to valid arraylist
                    valids.Add(servant);
                }
            }
            return valids;
        }

.NET 2.0 and generics help you do this in a much easier way, and takes away the need of creating a specific method or specific code block just to select a few objects:

            List<Servant> valids= servants.FindAll(delegate(Servant servant) { return servant.Sex == SexFlag.Female && servant.Age >= 18 && servant.Age < 26; });

The nice feature here is something called anonymous delegates, and if you saw what i wrote in the start of the posting, Predicate is a delegate, and you can actually create it on the fly as you need it. Neat right :-D.

Another neat thing is if you need to find a specific item, you can do it by using the Find method:

            string wantedServantName = "Jane";
            Servant jane = servants.Find(delegate(Servant servant) { return servant.Name.ToLower() == wantedServantName.ToLower(); });

I must say that when I found these two features, and also the ForEach method, which makes the foreach loop on a List almost obsolete:

            servants.ForEach(delegate(Servant servant) { servant.Age += 1; });

So what are you waiting for, generics, .NET, now :-D