Professional Documents
Culture Documents
Using arrays
Arrays provide a way to create strongly typed collections, for example:
However, they are very limited: they can’t grow (they are rather static), and you
don’t have any convenience methods (like sorting, iterating …). In some cases
arrays will be sufficient, but mostly you need more flexibility. And more
flexibility we get with the .NET collection classes, like ArrayList.
Page 1 of 51
Ctg Technical Articles
Create a little console application, call it Generics1, and add following code in
the Main method:
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
namespace Generics1
{
class Program
{
static void Main(string[] args)
{
// Create collection of numbers
ArrayList numbers = new ArrayList();
numbers.Add(1);
numbers.Add(5);
Console.WriteLine(totalSum);
}
}
}
It works perfectly, and the program will write the result (6) to the console.
But this is not very elegant code, and I’ll tell you why. First of all, an ArrayList
is not strongly typed. And if you look at the signature of the Add method, you’ll
see that it accepts a parameter of type object. So instead of adding integers to
the collection, you can add every kind of object, for example, a string:
numbers.Add("some string");
This is a perfectly legal code, but when you run it, an InvalidCastException
is thrown because in the loop it will try to add the string to a number. Of course,
we know that adding a string is not valid, but the compiler will allow it anyway:
there’s no compile-time checking possible. Also, there’s a lot of casting going on:
when you add a number to the collection, there’s an implicit upcast to an object;
and visa versa: in the loop the objects are unboxed again to integers. Boxing and
unboxing are very time-consuming operations and should be avoided, because
they degrade performance. This effect of boxing/unboxing can be quite
significant in scenarios where you must iterate large collections.
Then add a new class called PersonCollection and create the strongly typed
collection:
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System;
using System.Collections.Generic;
using System.Text;
namespace Generics2
{
class Program
{
static void Main(string[] args)
{
// Create some persons
Person person1 =
new Person("Ludwig Stuyck", "Zaventem", 33);
Person person2 = new Person("Leen Rans", "Zaventem", 25);
As you see, we can only add objects of type Person to the person’s collection.
If we would add another type of object, the compiler would give an error. So we
have created a strongly typed collection that only accepts Person objects;
however, there’s still a lot of casting going on in our PersonCollection class.
As expected, the output is:
Generics
Time to introduce a new feature of .NET framework 2.0: generics. Generics in C#
support defining algorithms and defer the data type until the point of use.
They are a type of data structure that contains a code that remains the same;
however, the data type of the parameters can change with each use. So in
general, it’s a code template that can be applied to use the same code, using
various types. They are also called parameterized types or parametric
polymorphism. Generics have a number of important advantages:
Create a new console application and call it Generics3. Again, add the business
object Person that we defined earlier. Then, in the Main method, create a
collection of Person objects as follows:
using System;
using System.Collections.Generic;
using System.Text;
namespace Generics3
{
class Program
{
static void Main(string[] args)
{
// Create some persons
Person person1 =
new Person("Ludwig Stuyck", "Zaventem", 33);
Person person2 = new Person("Leen Rans", "Zaventem", 25);
You can immediately see the huge advantage because we don’t have to write our
custom collection classes (like PersonCollection) anymore! Moreover, there’s
no need for casting objects.
We could in the same way define and use a collection that holds integers:
// Calculate sum
int totalSum = 0;
foreach (int number in numbers)
{
totalSum += number;
}
Console.WriteLine(totalSum);
When the Find method is called, the FilterByName method is executed for
each person in the collection, until the expression in the method returns true.
When the method returns true, the current Person object will be returned by the
Find method. When the method never returns true (so the person that is
searched for is not in the collection), then the Find method will return null.
{
private string location = string.Empty;
And again we can use the Find method to look for the persons that lives in
Zaventem:
// Create a new person location filter, that will look for the specified
location
PersonLocationFilter personLocationFilter = new
PersonLocationFilter("Zaventem");
// Create a new predicate, that uses the FilterByLocation method
// to determine whether the person has been found
Predicate<Person> filterByLocation = new
Predicate<Person>(personLocationFilter.FilterByLocation);
// Find the person in the collection
Person foundPerson = persons.Find(filterByLocation);
However, there is more than one person living in Zaventem, and we want to find
them all. We can do so by using the FindAll method:
int oldestAge = 0;
foreach (Person person in persons)
{
if (person.Age > oldestAge)
oldestAge = person.Age;
}
Console.WriteLine("{0}", oldestAge);
However, there’s a new, more object oriented way of doing this. First, create a
class PersonAgeCalculator that is responsible for the calculation, as follows:
And then, create an Action object and pass it on to the ForEach method of the
person collection:
As you see, the Person class knows now that it has to compare the Name property
if two objects are compared. Now we can use the Sort method:
Console.WriteLine();
// Sort collection
persons.Sort();
So sorting works. There is still room for some improvement here. Notice that
there was still casting needed in the CompareTo method, when we implemented
the IComparable interface. Indeed, the IComparable interface forces us to
implement the CompareTo method, which takes an object as a parameter:
As we have said before, we should avoid casting, and that’s what we’ll do next.
The solution to this problem is generic interfaces. Instead of implementing the
IComparable interface, we will implement its generic counter part, the
IComparable<Person> interface:
Extending sorting
In the previous example we implemented sorting. But, we did not have the
possibility to specify on which property the sort should be done. It would be nice
if we could sort the collection by another property. First of all, we will create our
own custom implementation of IComparer. We’ll add a new class
PersonComparer, that implements the generic interface IComparer<Person>.
This class knows about Person objects, and knows how to sort them. But before
we do that, create an enumeration with supported comparison types:
Name,
Location,
Age
};
The idea is to pass an instance of this class to the Sort method of the List. In
the class we implemented the method Compare, and here we use a new method
CompareTo of the Person objects to do the comparison, passing the
ComparisonType. So we will add this method to the Person class.
Depending on the passed comparison type, the correct properties are compared
to each other. Now we can use the PersonComparer class to sort the collection,
for example using age:
persons.Add(person2);
persons.Add(person3);
Console.WriteLine();
Now you can use the SortOrder property to set the sort order:
Console.WriteLine();
Generic classes
You can also create your own generic classes. Generic classes encapsulate
operations that are not specific to any particular data type. As an example, let’s
create our own generic class Tree. This tree will represent an in-memory
hierarchical representation of objects of type T. Create a new console application
and call it Generics4. Add the Person class again and then define a generic
class Tree as follows:
If we want to use this tree (of course, the Tree class does not do anything yet) in
our code, we could create an instance as follows:
// Create the generic tree that will work with Person objects
Tree<Person> personHierarchy = new Tree<Person>();
}
Let’s implement the tree now. A tree consists out of tree nodes that can work
with this type T, so we’ll need a TreeNode class, which is also a generic class:
The top of the tree hierarchy is a root node, so we’ll add a RootNode property to
the Tree class, so this is a generic property:
Note the default () operator, which returns the default value of a type. It can be
used to initialize an object to its default value.
// Create a tree node object, that will work with Person objects
TreeNode<Person> grandFatherNode = new TreeNode<Person>();
Each tree node we create, has to have a reference to a particular object of type T.
Therefore, we will add a Tag property to the TreeNode class, this also is a
generic property:
public T Tag
{
get { return tag; }
set { tag = value; }
}
}
This forces us to assign an object of type T to a tree node each time we create a
tree node. So we need to modify our code a little bit to be able to pass a Person
object when we create a tree node:
Right, this works… Now we will rewrite the Tree class a bit, so that it creates
tree nodes for us. Therefore we will add a method CreateNode to the Tree
class that will create and return a tree node for us. This is a generic method:
So now, instead of creating the tree node ourselves, we will use the new method
we have just created:
There’s something missing: our tree has a root node, but a root node should have
child nodes. So add a property Nodes to the TreeNode class, and this should be a
list of tree nodes:
{
get { return nodes; }
set { nodes = value; }
}
// Let the tree create and return a tree node (assigned to the
// person object we just created, and assign this tree node to
// the root node
personHierarchy.RootNode = personHierarchy.CreateNode(grandFather);
treeNodedaughter.Nodes.Add(treeNodegrandDaughter);
}
We have now created a tree with a root node, this root node has three child
nodes, and one of the child nodes has two child nodes. Each node is assigned to
an object of type Person. So what we have created is this hierarchy:
Louis
+-- Michael
+-- Jan
+-- Jennifer
+-- Kobe
+-- Eowyn
Note that we can easily reuse the Tree to work with other types. For example,
we could create a tree that works with strings, Customer or any other type.
And that’s the whole idea of using generics: write once, use many.
Derivation constraints
We want to implement sorting capability in our tree. Sorting simply means that
we need to sort each child node collection in the tree. But before we do this, first
write some code to display the tree hierarchy to the console:
// Let the tree create and return a tree node (assigned to the
// person object we just created, and assign this tree node to
// the root node
personHierarchy.RootNode = personHierarchy.CreateNode(grandFather);
Now we’re ready to implement sorting. It would be nice if we could just say to
the Tree object that it should sort its nodes. So we’ll add a Sort method to the
Tree class. This method will then call the Sort method of the root node, which
will cause a recursive sorting mechanism: the root node will sort its child nodes
collection and for each child node, it will call the Sort method again (recursive
method).
However, there’s a problem. A tree node does not now how to sort its collection
of tree nodes. Therefore we need to make sure that the TreeNode class
implements the IComparable interface, because when we do so, it forces us to
implement the CompareTo method in the TreeNode class. In this method, the
comparison method of the objects of type T will be called, which means that each
Tag property has to be compared. In our case, it means that each Person has to
be compared. The Person class knows how to compare itself, because we have
already implemented the IComparable interface previously.
The first step is to make sure that tree nodes know how they have to compare to
each other, by implementing the IComparable<TreeNode<T>> generic
interface:
Next, as we’ve said, the tree should have a Sort method, that will call the root
code Sort method:
Evidently, the TreeNode class should also have a Sort method, which will first
sort its child nodes collection, and then call itself again for each child node:
This won’t compile yet, because we take for granted that the type T implements
the IComparable interface. Imagine we would use the tree with an object type
that cannot compare itself (it does not implement IComparable…) then we
would have major problems, because sorting would not work. So our tree should
enforce user to use a type T that implements the IComparable interface: we
have to add a constraint to the Tree class:
By doing this, we say that the type T must implement the IComparable<T>
interface; and therefore are sure that this type T knows how to compare itself;
and sorting can work correctly.
Still one more thing to do: the type T defined in the Tree class is also used in the
TreeNode class. So we also need to make sure that the type T in the TreeNode
class implements the IComparable<T> interface:
// Let the tree create and return a tree node (assigned to the
// person object we just created, and assign this tree node to
// the root node
personHierarchy.RootNode = personHierarchy.CreateNode(grandFather);
Console.WriteLine();
// Sort hierarchy
personHierarchy.Sort();
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
namespace Generics
{
class Program
{
static void Main(string[] args)
{
// Create the generic tree that will work
// with Person objects
Tree<Person> personHierarchy = new Tree<Person>();
personHierarchy.CreateNode(son1);
personHierarchy.RootNode.Nodes.Add(treeNodeSon1);
Console.WriteLine();
// Sort hierarchy
personHierarchy.Sort();
{
get { return name; }
set { name = value; }
}
}
}
Constructor constraints
Suppose you want to instantiate a new generic object inside a generic class. You
have to be sure that the type that is passed actually has a public default
constructor. You can enforce this by using the new()constraint. Let’s have a look
at the following generic class:
When you try to compile this, you’ll see that there’s a compiler error ‘Cannot
create an instance of the variable type 'T' because it does not have the new ()
constraint’. So the compiler is not sure that type T has a public default
constructor, because we have not enforced it. We can do so as follows:
{
}
SortedList<>
Represents a sorted list.
Queue<>
Represents a first-in, first-out collection.
Stack<>
Represents a last-in, first-out collection.
Dictionary<>
Represents a collection that associates a key to a value.
SortedDictionary<>
Represents a sorted collection that associates a key to a value.
LinkedList<>
Represents a double linked list.
{
get { return name; }
set { name = value; }
}
Now don’t forget to build the project at this point – otherwise the Person class
will not show up in the following step.
Then select Object, because we are going to add our custom type as a data
source.
Click the Next button, and the following window will appear:
Select the Person class, and click the Finish Button. As you will see, Visual
Studio has added a DataSources folder to the project with a
Person.datasource configuration file, which is used to store generic object
data source configuration information:
Now create a BindingSource object and set its data source to the collection we
just created:
With this little piece of code we already have setup databinding! You can modify
and delete a field in the grid and it will be reflected in the data source (and vice
versa). However, if you now try to add a new row, you will get an error
‘Constructor on type not found’. This is because we have no default constructor
in our Person class, so we’ll add it:
…
}
Adding a BindingNavigator
Drop a BindingNavigator control to the form and call it
bindingNavigator, and set the DockState of the DataGridView to Fill:
Now run the application again. You will have full navigation support:
using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
namespace Generics5
{
public lass CustomBindingList<T> : BindingList<T>
{
}
}
#region IComparer<T>
#endregion
// Return result
return result;
}
// Return value
return propertyInfo.GetValue(value, null);
}
}
if (items != null)
{
PropertyComparer<T> propertyComparer
= new PropertyComparer<T>(property, direction);
items.Sort(propertyComparer);
isSorted = true;
}
else
{
isSorted = false;
}
sortProperty = property;
sortDirection = direction;
}
}
}
After a sort we need to let the bound controls know that sort order has changed,
which is a list-wide change. We do this by calling the OnListChanged method
after the sorting algorithm:
if (items != null)
{
PropertyComparer<T> propertyComparer
= new PropertyComparer<T>(property, direction);
items.Sort(propertyComparer);
isSorted = true;
}
else
{
isSorted = false;
}
sortProperty = property;
sortDirection = direction;
this.OnListChanged(new
ListChangedEventArgs(ListChangedType.Reset, -1));
}
Next thing to do is to override the IsSortedCore property, to make sure that the
bound controls know that sorting is done:
When sorting is removed, we also need to set the internal state to reflect this by
overriding the RemoveSortCore method:
dataGridView.DataSource = bindingSource;
And if you now start the application, and click on a column, it will be sorted:
public PropertyValueFilter(
PropertyDescriptor property, string value)
{
this.value = value;
this.property = property;
}
In fact, we now have done all that is necessary to support searching, so let’s
search! Add a TextBox to the bindingNavigator control and call it
toolStripSearchNameTextBox. Also add a Button and call it
toolStripSearchButton. Double click on the button to start implementing
it’s Click event handler:
Now start the application, type in a name in the text box, click the button and if
the name is in the grid, its row will be selected:
this.ClearItems();
if (File.Exists(filename))
{
BinaryFormatter formatter = new BinaryFormatter();
this.OnListChanged(new ListChangedEventArgs(
ListChangedType.Reset, -1));
}
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
One more thing: in order to be able to serialize a collection of objects, the object
itself should be marked as serializable. So modify the Person class and
decorate it with the Serializable attribute:
[Serializable]
public class Person
{
public Person()
{
}
…
}
using System;
using System.Collections.Generic;
using System.Text;
using System.Data.DLinq;
using System.Xml.XLinq;
using System.Query;
namespace Generics6
{
class Program
{
static void Main(string[] args)
{
// Create some persons
Person person1 =
new Person("Ludwig Stuyck", "Zaventem", 33);
Person person2 = new Person("Leen Rans", "Zaventem", 25);
Person person3 = new Person("Paulien Rans", "Rotselaar", 2);
Now let’s use LINQ to query this collection and find out what persons live in
Zaventem:
foreach(var val in q)
{
Console.WriteLine("{0},{1}, {2}", val.Name, val.Location, val.Age);
}
Thanks to Pascal Desmet, Jort Marievoet, Bart Kuppens, Olivier Van Hege, Dick
Bevernage, Annelies Aerts and James Curran.
About Ctg
CTG was founded in 1966 in the USA and expanded to Europe in 1976. Our IT
professionals deliver our services through a network of ISO-certified sites in
North America and Europe. CTG Europe provides services in Belgium,
Luxembourg, France, and the United Kingdom.
Website: http://www.ctg.com