Dot Net

Syrinx .NET Development Blog
Need help on your project? info@syrinx.com, or toll free (888) 579-7469, press 1

July 2008 - Posts

Converting Strongly Typed Collections to Generics: Part 2

In Part 1, the idea of converting your strongly typed collections to Generics was introduced.  Backward compatibility and existing client support was maintained.  However once a level of acceptance has been maintained for the new base class and the reduced amount of code in the collection classes, new development can utilize new constructs in c#3.0 which can reduce the code in your strongly typed collections even more, as well as eliminating the strongly typed collections all together.  See the code snippet below which shows the base generic class.

public class BaseGenericCollection<T> : System.Collections.ObjectModel.Collection<T>
{
    internal List<T> m_deletedList = null;

    new public void Add(T t)
    {
        if (this.Contains(t))
            throw new System.ArgumentException("Cannot add duplicate entry to collection.");
        base.Add(t);
    }
    public void Delete(T t)
    {
      if (t == null)
      {
   throw new ArgumentNullException("T", "BaseGenericCollection: Cannot Delete NULL from collection.");
      }
      if (m_deletedList == null)
      {
          m_deletedList = new List<T>();
      }
      this.m_deletedList.Add(t);
      base.Remove(t);
    }
}

The base class now no longer needs the GenericSorter<T> internal class it had in Part 1.  The strongly typed StudentCollection is also no longer needed, but can be kept around for backward compatibility.  However the same functionality can still be achieved, along with type safety, using anonymous methods, or even cleaner syntax with lambda expressions.   Lambda expressions simplify the syntax of defining at run time, logic that can be invoked in response to an event without needing a dedicated method for the event handler.

Consider the following code which illustrates using lambda expressions to sort a collection that has a new base class.

static void Main(string[] args)
    {
      //Load up a Generic collection of Student objects
      BaseGenericCollection<Student> _students = LoadStudents();

      //Notice that the Students can be ordered within the loop using the OrderBy method from the extension
      //methods from System.Linq within CollectionBase<T>
      //and the syntax can be reduced using the Lamda operator instead of an anonymous  method
        foreach (var student in _students.OrderBy(student => student.Major))
        {
          Console.WriteLine("{0,-10} \t{1,-10} \t{2,-10} \t{3,-10:d} \t{4,-10:D} ",
   student.FirstName, student.LastName, student.Major,
   student.EnrollmentDate, student.GraduationDate);
        }


The local student collection instance, _students, is now a BaseGenericCollection<Student> type and still has all the capabilities it had previously but with less code.  The base class’ base class System.Collections.ObjectModel.Collection<T> has an extension method in System.Linq for sorting called OrderBy that requires to be passed a delegate, which can be replaced with a lambda expression.

 

New Constructs

C# 3.0 supports the capability of handling events inline by assigning or delegating a chunk of code directly where an event handler will be referenced, instead of creating a dedicated method to respond to an event.  This approach takes advantage of anonymous methods.  The syntax can be a little “too busy” or confusing.  The syntax can be cleaned up using lambda expressions, which are cleaner, more simply ways of creating anonymous methods.

The MSDN Library defines a lambda expression as “… an anonymous function that can contain expressions and statements, and can be used to create delegates or expression tree types.  The syntax can be broken down as: arguments to be processed => (goes to) statements to be processed.   In the example above, the OrderBy method is passed a student instance (to be evaluated once it is referenced via the foreach loop) which is the object to be processed, then the “goes to” operator (=>)and student.Major, ( the value of the student’s major), which is the statement that is to be processed.  The OrderBy method actually considers it like an identity function according to the MSDN Library.

To illustrate lambda expression or more so, the statements to be processed consider the following example.

//First define the major we are looking for
Program._majorToFind = "Physics";

//Now we can loop thru the collection that is returned from casting to a List<Student> collection and using the FindAll method, using the Lambda operator
foreach (var student in _students.ToList<Student>().FindAll
    (student => student.Major == Program._majorToFind))
    {
        Console.WriteLine("{0,-10} \t{1,-10} \t{2,-10} \t{3,-10:d} \t{4,-10:D} ", student.FirstName,
        student.LastName, student.Major, student.EnrollmentDate, student.GraduationDate);
    }

//Now we utilize the Find method, to find a specific instance with specific data, using the Lambda operator
Program._lastNameToFind = "Valenti";
Student students = _students.ToList<Student>().Find(student => student.LastName == Program._lastNameToFind);

 

 

Notice the foreach, the type that is being retrieved is defined as a var which is an anonymous type that is ultimately returned from the Find and FindAll methods of List<T>.  Those methods expect to receive a System.Predicate<T>, which is ultimately a delegate or method pointer.  As illustrated above, a method pointer can be replaced by an anonymous method which also can be replaced by a lambda expression.  The statements to be processed in the lambda expression are looking for student instances with a student  Major property value of “Physics” and only return instances with those values.

The same is true for the Find method of List<T>.  It requires a method pointer which can be replaced with a lambda expression that returns the student object with the LastName property equal to Program._lastNameToFind.

Your strongly typed collection code can be further reduced than that illustrated in Part 1, by utilizing new constructs added to the c# language.  These new objects and constructs actually make it more readable and understandable once you understand how they are read.  All the compiler time type checking is maintained and the compiler infers the correct type for the anonymous types used in the lambda expressions.

In Part 3, the lambda expression will be replaced with Language Integrated Queries (Linq).