Introduction to generics in C#

We will start by talking about generics in the C# language, which are the implementation mechanism for parameterized classes.

In a series of articles that I have prepared for .com, I am going to describe some of the fundamental characteristics of the C# and VC.NET language, which I have been talking about in my

Generics are the parameterized class implementation mechanism introduced in version 2.0 of the C# language. A parameterized class is exactly the same as a regular class, except for one small detail: its definition contains some element that depends on a parameter that must be specified when an object of said class is declared.

This can be extremely useful when programming generic classes, capable of implementing strong typing without the need to know a priori the types for which they will be used. Confused? We better see it with an example.

We know that an ArrayList is a wonderful container of elements and that, fortunately or unfortunately, depending on how you see it, it works with the base type object. This makes it possible to store references to any (ie all) descendant object types of this type in it, although this advantage becomes inconvenient when it comes to controlling the allowed object types. In other words, nothing prevents the following:

ArrayList al = new ArrayList();
al.Add(“Seven horses come from Bonanza…”);
al.Add(7);
al.Add(new String(‘*’, 25)); // 25 asterisks
This can cause errors when retrieving the elements of the list, especially if we assume that the elements must be of a certain type. And of course, the problem is that the error would occur at runtime, when it is often too late:
foreach (string in al)
{
System.Console.WriteLine(s);
}

Effectively, an exception is thrown stating that “Cannot cast an object of type ‘System.Int32’ to type ‘System.String'”. Logical.

Obviously that can be easily solved, for example by creating our own collection starting from CollectionBase (or similar) and showing access methods to strongly typed elements, or, using delegation, creating a class from scratch that implements interfaces like IEnumerable on which inside there is a collection that is the one that actually performs the work.

In any case, it is a lot of work, since for each class that we want to contemplate we should create a specific class, as described in the previous paragraph.

And this is where generics come into the picture. The following code declares a list of elements of type ComplexSomething:

List list = new List();
SomethingComplex something = new SomethingComplex();
list.Add(something);
list.Add(new SomethingComplex());
list.Add(“blah”); // Compilation failed!

See also  Differences between HTML and XML

With this declaration, it will not be possible to add objects that are not of the indicated class to the list, nor will it be necessary to perform a cast when obtaining the elements, since they will be directly of that type.

It is interesting to see the large number of generic classes for handling collections that were introduced with version 2.0 of the framework in the System.Collections.Generic namespace, and their widespread use throughout the framework.

Creating generic classes

In the previous examples we have seen how to use the generic classes provided by the framework, but what if we want to create our own generic class? We will see that it is very simple.

We are going to develop a complete example where we can see the syntactic peculiarities and details to take into account. We will create the ObjectStorage class, whose mission is to store a generic object and allow us to retrieve it at any time. Basically, we’ll build a class with an instance variable and a getter and setter to access it, but we’ll use generics to ensure that it works for any data type and that the object entered is always the same type as the object being retrieved:

public class ObjectKeeper
{
private T object;
public T Object
{
get { return object; }
set { this.object = value; }
}
}

As we can see, in the very definition of the class we indicate that this will be a template that will receive as a generic parameter a type that we have called T in the previous example.

You can see how we have declared a private member called object, of type T, which can be accessed through the corresponding getter and setter. The specific type on which we will act will be defined when an object is instantiated, since it will have to be expressly indicated:

// Create a string keeper…
ObjectKeeper cs = new ObjectKeeper();
// Create a keeper of ints
ObjectKeeper ci = new ObjectKeeper();
// Create a custodian of Things
ObjectKeeper cp = new ObjectKeeper();

In this way, we avoid having to create specific classes for each type of data with which we need to work, saving us a lot of effort and maintaining all the advantages that strong typing offers us.

And to give you an idea of ​​the potential of this technique, consider that if genericity did not exist (as it did in previous versions of the language), we would be forced to implement specific classes such as the following:

See also  Number format in PHP

public class StringKeeper
{
private string object;
public string Object
{
get { return object; }
set { this.object = value; }
}
}
public class KeeperOfThings
{
private Thing object;
public Thing Object
{
get { return object; }
set { this.object = value; }
}
}
// … etc.

The following code shows the use of the new generic ObjectKeeper class that we defined earlier:

ObjectKeeper cs = new ObjectKeeper();
cs.Object = “Hello”; // We assign directly
string s = cs.Object; // No need for a cast,
// since Object is of type string

cs.Object = 12; // Compilation error,
// Object is of type string
ObjectKeeper ci = new ObjectKeeper();
ci.Object = 12; // We assign directly
int i = cs.Object; // No cast is necessary, since Object is int

cs.Object = “Hello”; // Compilation error,
// Object is of type int

Type restriction in generics

An interesting feature in the declaration of generics in C# is the possibility of setting limitations on the types used as template parameters. Although it may seem paradoxical, the language itself makes it easy to create generic classes that are not so generic, which can be really useful in multiple scenarios.

And to see the meaning of this, let’s use the following example, apparently correct:

public class Selector
{
public T Major(T x, T y)
{
int result = ((IComparable)x).CompareTo(y);
if (result > 0)
return x;
else
return y;
}
}

It is an open generic class, whose only operation (Greater(…)) allows obtaining the largest object of the two that we pass to it as parameters. The comparative criterion will be set by the class itself on which the template is instantiated: the integers will be according to their value, the strings according to their alphabetical order, etc.

Next we create two instances starting from the previous template, and specifying the generic to the types that we need:

Selector selInt = new Selector();
Selector selStr = new Selector();

These two instantiations are totally correct, right? If after them we use the following code:

Console.WriteLine(selInt.Major(3, 5));
Console.WriteLine(selStr.Major(“X”, “M”));

We will get a 5 and an X on the console. Everything is perfect; For each call, the conversion to string of the largest object of the two that we have sent as parameters appears.

The problem appears when we instantiate the generic class for a type that does not implement IComparable, which is used in the Greater() method to determine the greater object of both. In this case, an exception is thrown in execution indicating that the cast to IComparable cannot be performed, aborting the process. For example:

See also  Photoshop Layers

public class MyClass // Not comparable
{
}

Selector sel = new Selector();
MyClass x1 = new MyClass();
MyClass x2 = new MyClass();
Console.WriteLine(selString.Major(x1, x2)); // Exception,
// are not
// comparable!

A possible solution would be, before casting to IComparable in the Greater() method, to do a type check and cast only if possible, but what if not? return a null? I don’t think this question has a simple answer, since in any case, you would be trying to compare two objects that cannot be compared.

The optimal solution, as almost always, is to control at compile time what could be a source of problems at run time. The C# specification includes the possibility of defining constraints in the generic class declaration, limiting the types with which the generic can be instantiated. And also, quite simply, just add the following where clause to the Selector class declaration:

public class Selector
where T: IComparable // We only allow comparables!
{
public Type Major(Type x, Type y)

There are several types of constraints that we can use to limit the types allowed for our generic types:

  • where T: struct, indicates that the argument must be a value type.
  • where T: class indicates that T must be a reference type.
  • where T: new(), forces type T to have a public parameterless constructor; It is useful when from within a method of the class it is intended to instantiate an object of the same.
  • where T: classname, indicates that type T must inherit or be from that class.
  • where T: interfacename, T shall implement the indicated interface.
  • where T1: T2, indicates that the argument T1 must be equal to or inherit from the type, also a generic parameter of the class, T2.

These constraints can be combined, so if we want a generic parameter to adhere to several of them, we simply separate them by commas in the where clause:

public class Selector
where T: IComparable, new() // We only allow comparables,
// instantiable without parameters

This information will be completed in the following article in which…

Loading Facebook Comments ...
Loading Disqus Comments ...