Thursday, 16 February 2017

IComparer

If you inspect the overloaded Sort() method for collections you'll see that it contains
public void Sort(IComparer<T> comparer);
and this version is a perfect example of the inversion of control pattern, the method expects a class that implements the IComparer interface, but leaves it up to the user as to how the implementation will work. here's the definition for the interface.

namespace System.Collections.Generic
{
// Summary:
//     Defines a method that a type implements to compare two objects.
//
// Type parameters:
//   T: The type of objects to compare. This type parameter is contravariant.
    public interface IComparer<in T>
    {
// Summary:
//     Compares two objects and returns a value indicating whether one is less than,
//     equal to, or greater than the other.
//
// Parameters:
//   x: The first object to compare.
//   y: The second object to compare.
//
// Returns:
//     A signed integer that indicates the relative values of x and y, as shown in the
//     following table.
//     Value                Meaning
//     Less than zero       x is less than y.
//     Zero                 x equals y.
//     Greater than zero    x is greater than y.
        int Compare(T x, T y);
    }

}

now we'll create a comparer for the following Person class

class Person {
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime BirthDate { get; set; }

    public int Age { get {
            var today = DateTime.Today;
            var age = today.Year - BirthDate.Year;

            return BirthDate > today.AddYears(-age) ? --age : age;
        }
    }

    public override string ToString()
    {
        return $"{Age} {FirstName} {LastName}";
    }

}

pretty standard class, exactly what we used in the previous post about IComparable<T>. Now let's go ahead and create our PersonComparer class which implements the IComparer<T> Interface.

class PersonComparer : IComparer<Person>
{
    PropertyInfo PropertyToSortOn = typeof(Person).GetRuntimeProperty("LastName");

    public PersonComparer(PropertyInfo Property)
    {
        if (typeof(Person).GetRuntimeProperties().Contains(Property))
            PropertyToSortOn = Property;
    }

    public int Compare(Person x, Person y)
    {
        switch (PropertyToSortOn.Name) {
            case "FirstName":
                return x.FirstName.CompareTo(y.FirstName);
            case "LastName":
                return x.LastName.CompareTo(y.LastName);
            default:
                return x.Age.CompareTo(y.Age);
        }  
    }

}

we used a little reflection here, to specify which property we want to sort on, and in this way we can define how we want to sort separately from our Person class.

class PersonComparer : IComparer<Person>
{
    public int Compare(Person x, Person y)
    {
        var lastName = x.LastName.CompareTo(y.LastName);
        if (lastName == 0)
            return x.FirstName.CompareTo(y.FirstName);
        return lastName;
    }

}

for good measure i also created a class that sorts by last name and if the last names are equal then sorts by first name.

anyway here's a main we can use to test our co

using System;
using System.Collections.Generic;

namespace pc.IComparer
{
    class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime BirthDate { get; set; }
        public int Age
        {
            get
            {
                var today = DateTime.Today;
                var age = today.Year - BirthDate.Year;

                return BirthDate > today.AddYears(-age) ? --age : age;
            }
        }
        public override string ToString()
        {
            return $"{Age} {FirstName} {LastName}";
        }
    }

    class PersonComparer : IComparer<Person>
    {
        public int Compare(Person x, Person y)
        {
            var lastName = x.LastName.CompareTo(y.LastName);
            if (lastName == 0)
                return x.FirstName.CompareTo(y.FirstName);
            return lastName;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            var p1 = new Person
            {
                FirstName = "Tomek",
                LastName = "Chooch",
                BirthDate = new DateTime(1988, 8, 28)
            };
            var p2 = new Person
            {
                FirstName = "Pawel",
                LastName = "Chooch",
                BirthDate = new DateTime(1984, 6, 28)
            };
            var p3 = new Person
            {
                FirstName = "Marin",
                LastName = "Smartz",
                BirthDate = new DateTime(1983, 11, 3)
            };
            var p4 = new Person
            {
                FirstName = "Magda",
                LastName = "Chooch",
                BirthDate = new DateTime(1984, 1, 31)
            };
            var p5 = new Person
            {
                FirstName = "Jakeb",
                LastName = "Tywonk",
                BirthDate = new DateTime(1988, 5, 4)
            };
            var p6 = new Person
            {
                FirstName = "Ivan",
                LastName = "Pendaz",
                BirthDate = new DateTime(1986, 3, 3)
            };
            var p7 = new Person
            {
                FirstName = "Ivan",
                LastName = "gajic",
                BirthDate = new DateTime(1983, 3, 12)
            };
            var p8 = new Person
            {
                FirstName = "Ivana",
                LastName = "Trudu",
                BirthDate = new DateTime(1986, 3, 1)
            };

            var ppl = new List<Person>(new Person[] { p1, p2, p3, p4, p5, p6, p7, p8 });

            var pc = new PersonComparer();
            ppl.Sort(pc);

            foreach (var p in ppl)
                Console.WriteLine(p.ToString());
        }
    }
}