Converting Strongly Typed Collections to Generics: Part 1
Many enterprise applications make use of strongly typed collections in their solution. Although they require more development time, the advantages they provide can outweigh the cost of the extra development time. Some of the advantages include compile time and run time type safety, processor time savings from not having to downcast, the time to search and perform type checking, and sorting on specific properties. .Net 1.1 contains base classes that provide all the typical methods and properties needed to build your own strongly typed collection class. But as previously mentioned extra development time is needed to develop a collection class for each business class that can exist in multiple, simultaneous versions at run time. This leads to higher development costs and higher maintenance costs. Business requirements grow over time, and new properties can be added to business classes that require sorting at the application tier, thus needing development time to update the collection class to allow sorting on the new properties. Also some properties can have their exposure increased and thus needing a new “FindBy…” method in the collection.
Much of this development time can be reduced using Generics, a new construct added to c# 2.0. However when environments and projects must adhere to consistent standards, before new technologies make their way into new development cycles, sometimes legacy solutions will be converted to the new technology prior to new functionality using it. Also if it is required to maintain either backward compatibility, or existing client code, removal of the existing strongly typed collections could be prohibited. However reducing its code base and maintaining its existing signature is possible with Generics.
The topic of converting all your strongly typed collection classes will be discussed and broken up into 3 separate parts. When large enterprise applications contain many strongly typed collection classes, converting them to Generics can contain large amounts of risk and could be separated into different steps that reduce risk, and keep a higher level of quality.
Here in Converting Strongly Typed Collections to Generics Part 1, the first step will be only to reduce the amount of code in each of the strongly typed collection classes by using Generics. The basic structure of a non generic strongly typed collection class will be discussed with an example of all the usual parts. Then the strongly typed collection class will have the majority of its code removed as well as changing its base class to use Generics, all the while maintaining backward compatibility.
Strongly Typed Collections
A strongly typed collection class has the typical methods and properties of any collection class but only accepts and processes 1 type. For example, see the code snippet below that illustrates some typical methods. Note this is only a snippet of the class; download the project to see the entire collection class as well as the complete example. The snippet below illustrates the typical properties and methods that a collection class and namely a strongly typed collection class will contain.
public class StudentCollection : BaseCollection
{
public Student this[int index]
{ //List handles throwing ArgumentOutOfRangeException
get {return (Student)List[index];}
set { List[index] = value; }
}
public int Add(Student student)
{
if (student == null)
{
throw new System.ArgumentNullException("student", "Cannot Add NULL to collection.");
}
// Is List a Unique list?
if (IsUnique && List.Contains(student))
{
throw new System.ArgumentException("Cannot add duplicate entry to collection.");
}
return List.Add(student);
}
public StudentCollection FindByMajor(string major)
{
StudentCollection studentCollection = new StudentCollection();
foreach (Student student in this.List)
{
if (student.Major == major)
{
studentCollection.Add(student);
}
}
return studentCollection;
}
Note the indexer only indexes on a
Student object, and the Add method only adds a Student type. Also the FindByMajor method is specific to a StudentCollection and searches only for Student objects.
When using the collection, it is pretty straight forward, you can new a Student object, add it to an instance of a StudentCollection, and find a specific Student instances. See the code example below.
Student student1 = new Student(1, "Ben", "Franklin", "Physics");
StudentCollection students = new StudentCollection();
students.Add(student1);
StudentCollection studentsToFind = students.FindByMajor("Physics");
Normally the collection will be filled at a database tier or other method, and the collection will contain more than 1 Student object. For simplicity only a snippet is shown here.
Generics
Now we will look at creating a Generic base class and removing the common code used by all collections.
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);
}
The base class will inherit from System.Collections.ObjectModel.Collection<T> and will contain all the typical properties and methods any collection class has, but it will be instantiated with a type and thus will maintain its type safety. Notice there is no “FindBy…” method, because we won’t know what type this class will be instantiated with.
Now let’s look at the Student Collection. Remember, we are only removing the typical parts of strongly typed collection into a reusable base class that maintains type safety. The StudentCollection class will still exist, but will only contain the custom methods and/or properties specific to a StudentCollection class.
public class StudentCollection : BaseGenericCollection<Student>
{
public StudentCollection FindByMajor(string major)
{
StudentCollection studentCollection = new StudentCollection();
//Create a temporary anonymous collection
//and use a lambda expression to only select those students with the desired major.
//The compiler will infer the type and maintain type safety
var students =
this.Select((student, index) =>
new
{
index, studentToFind = student.Major == major
}
);
//Loop thru the anonymous type
foreach (var student in students)
{
if (student.studentToFind)
{
studentCollection.Add(this[student.index]);
}
}
return studentCollection;
}
}
The StudentCollection class inherits from our BaseGenericCollection which is really a Collection<T> of Student objects which provides the type safety that was required in the legacy application.
We can also take advantage of other new constructs in c#3.0, namely anonymous types and lambda expressions. An anonymous type can be used to select a collection of student objects that meet the criteria using lambda expressions. The lambda expression reduces the amount of code compared to an anonymous method.
Upgrading your strongly typed collections to Generics can reduce your code base, and time to maintain the classes. When there are several strongly typed collections and they are used throughout the application, taking small steps in upgrading them will reduce the risk of introducing defects, and keep the quality at a level that is arguably acceptable by all the teams.
Next in Part 2 we will discuss removing all the strongly typed collections and having only 1 collection used throughout your application. The type safety will still be maintained and the “FindBy…” methods will be moved to the tier that instantiates the Generic base class.