Wednesday, February 1, 2012

C# Generics


Often times you find yourself writing similar functions/classes just to perform the same operation on different types of data. Some examples of this are sorting, swapping and searching. Let us look at one case of this: the linear search. The idea with linear search is to iterate through a structure until you find what you are looking for.

This is an implementation for an array of integers:
Code:
 int[] v = {1, 3,2,4,6,7};
 int target = 6;
 static int linSearchInt(int[] v, int target) {
      for(int i=0;i<v.Length;i++) {
         if (v[i] == target) {
              return i;
           }
      }
     return -1;
 }
Now suppose we want a linear function for an array of doubles. We can then write this:
Code:
static int linSearchdouble(double[] v, double target) {
      for(int i=0;i<v.Length;i++) {
         if (v[i] == target) {
              return i;
           }
      }
     return -1;
 }
The problem here is both methods are the same and if we make a change to one we have to make the change to both functions. Another problem is we need one version of this method for every data type that we want to support. With generics we can write one function that works on any type (with some restrictions).

Observe this code:
Code:
static int linSearch<T>(T[] v, T target) {
            for (int i = 0; i < v.Length; i++)
            {
                if (v[i].Equals(target)) return i;
            }
            return -1;
        }
This is a generic method. To make a method generic we have to declare generic types. In C# generic types are declared after the method name in angle brackets. In our case we have one generic type which is called T. Then you notice that the array is of type T and the target is also of type T. For something to be in the array it has to be the same type as the array.
Notice now that Equals method is being used instead of the == operator from the above examples. Since T can be any object we cannot use == and expect it to work. It would only work if the object defines the == operator. Because of this problem you cannot use == and have to use .Equals to compare equality. You can use this method on types you define yourself as long as you define an Equals method.

Calling Generic Methods
To call the method you simply provide parameters to the method as you normally would.
Like this:
Code:
  int[] v = {1, 3, 2, 4, 6, 7};
int target = 4; int index = linSearch(v, target);
Console.WriteLine(index);
The output is:
3
Once the first parameter is passed, c# requires the second parameter be of the same type. If you pass an integer array and a double the program will not compile.
Now let us try calling the above method with an array of doubles.
Code:
 double[] v = {1, 3, 2, 4, 6, 7};
 double target = 4;
 int index = linSearch(v, target);
 Console.WriteLine(index);
Notice the only thing that changed was the type of the parameters. The function call did not change and how we use the return value also did not change.

Using Our Own Types

If we want this method to work on our types we have to specify how two objects are equal. Let us look at the Person class again from previous tutorials:
Code:
    class Person
    {
        public int Id { get; set; }
        public double Height { get; set; }
        public int Age { get; set; }
        public DateTime RegisteredDate { get; set; }

        public override bool Equals(object obj)
        {
            var p = obj as Person;
            if (p == null) return false;

            return (p.Id == Id && p.Height == Height && p.Age == Age
                    && p.RegisteredDate == RegisteredDate);

        }

        public override String ToString()
        {
            return string.Format("Id: {0} Height: {1} Age: {2} RegisteredDate: {3}", Id, Height, Age, RegisteredDate);
        }
    }
The change is the Equals method. First if the parameter obj cannot be converted to a person then they cannot be equal. Then we define two objects to be equal only if all their fields are the same.
Code:
var person1 = new Person { Id = 0, Age = 3, Height = 10, RegisteredDate = new DateTime(2011, 3, 3) };
            var person2 = new Person { Id = 1, Age = 3, Height = 12, RegisteredDate = new DateTime(2012, 8, 4) };
            var person3 = new Person { Id = 2, Age = 5, Height = 10, RegisteredDate = new DateTime(2012, 8, 4) };
            var person4 = new Person { Id = 3, Age = 7, Height = 8, RegisteredDate = new DateTime(2010, 4, 5) };
            var person5 = new Person { Id = 4, Age = 6, Height = 9, RegisteredDate = new DateTime(2009, 6, 8) };
            var person6 = new Person { Id = 5, Age = 4, Height = 6, RegisteredDate = new DateTime(2008, 5, 4) };
            var person7 = new Person { Id = 6, Age = 5, Height = 4, RegisteredDate = new DateTime(2008, 5, 4) };
            var person8 = new Person { Id = 7, Age = 3, Height = 10, RegisteredDate = new DateTime(2008, 5, 4) };
            var persons = new [] { person1, person2, person3, person4, person5, person6, person7, person8 };

            if (linSearch(persons, new Person { Id = 9, Age = 3, Height = 10,      RegisteredDate = new DateTime(2008, 5, 4) }) != -1)
                Console.WriteLine("Found");
            else
                Console.WriteLine("not found");

Then calling the method is as simple as passing in an array and an object of the same type as the array. Once again no change is needed to the method.

No comments:

Post a Comment