Introduction to generics

My next blog post will cover a few topics regarding generics in AX 7 and because many X++ developers aren’t familiar with this concept (as it’s not used in X++ directly), I thought it would be to explain what it is.

Imagine that you’re designing a collection class (such as a list) in a statically-typed object-oriented language and you want this collection to be usable for any type.

To define a single class supporting all types of objects, you can let it store instances of the Object class. Because everything is an object, you can successfully put anything there. It may look like this:

public class List
{
    public void Add(Object value) {}
    public Object GetFirst() {}}

I realize that it’s a bit different in X++, because primitive types don’t extend from Object and it offers anytype, but let’s stick to the traditional object-oriented design.

Such a collection class will work, but it’s not very safe, because it’s possible to inadvertently mix values of different types. Also, you have to cast values from Object back to the expected type, which will fail at runtime if you use a wrong type (although it compiles correctly).

For example, the following code tries to create a list of numbers, but one of the values is a string.

List numbers = new List();
numbers.Add(1);
numbers.Add(2);
numbers.Add("3");

Because the collection accept all objects, this code is valid and you have no way how to enforce it to accept only numbers at compile time.

If somebody iterates the list and try to assign each value to an int variable, it will throw a runtime exception at the third element.

We could create a separate class for a list of integers, another for a list of strings, yet another for a list of customers and so on, which would allow us to detect such error during compilation, but it would be completely impractical. Here is when generics come to rescue.

With generics, we can define the common logic in a generic class and leave out the specific type of values. For example, we’ll always have an Add method accepting a value of a certain type, but we don’t have to specify which type it will be. The type will be selected by developers using our generic class.

Using the C# syntax, the definition of our generic list will look like this:

public class List<T>
{
    public void Add(T value) {}
    public T GetFirst() {}}

T is a placeholder for the specific type. It’s used in the class name (making clear that this is a generic class with a single type parameter) and at all places where we want to use the type (such as the parameter of Add()).

When we want to use this generic class, we always have to specify which type it will store. For example, List<int> is a collection of integers, List<Customer> a collection of customers and so on. The compiler replaces all occurrences of T with the given type, therefore List<int> will behave like if it was defined in this way:

public class List
{
    public void Add(int value) {}
    public int GetFirst() {}}

This is a very different from using a list accepting all objects – trying to add anything else than int will cause a compilation error, the return type of GetFirst() is int and therefore there is no need for casting from Object and so on. It allows us to define our list and all the common logic just once and still have a potentially infinite number of strongly-typed list classes for any existing or future type.

Generic methods

The same concept of generics can also be applied to individual methods.

The snippet below shows how you could declare a method for reversing lists, which accepts one list and returns another list of the same type.

List<T> reverseList<T>(List<T> listToReverse)
{}

Constraints

Sometimes you don’t want to support all types, but you have certain limitations – it must be a class, it must implement a certain interface or something like that. C# allows adding these constrains by a kind of where class, as documented in Constraints on Type Parameters (C# Programming Guide).

The next example shows a method for creating instances of various classes by calling their public parameterless constructor. Because not every type has such a constructor, I had to add a constraint for such types (where T : new()) – trying to call new T() wouldn’t compile if I omitted the constraint.

public T CreateObject<T>() where T : new()
{
    return new T();
}

Covariance and contravariance

Without going into details, it’s worth mentioning that the type parameter you use doesn’t always have to match the declared type exactly. In some situations, you can use compatible type. For example, if a method is declared to accept IEnumerable<FileSystemInfo>, you can send there an instance of List<DirectoryInfo> (FileSystemInfo is a parent of DirectoryInfo). If you with to learn more, look at Covariance and Contravariance on MSDN.

Conclusion

With generics, you can write code capable of working with many different types and still keep strict type control at compile time. It’s commonly used in many modern programming languages, such as C#, Visual Basics and Java.

Leave a Reply

Your email address will not be published.