How to work with generics in C#

Take advantage of generics to eliminate redundant code, enforce type safety and promote code re-usability and maintainability

How to work with generics in C#

Generics

C# is a strongly typed language. This implies that when using C# you should declare a type prior to storing data in it. Though this type safety helps enforce safety and interoperability between languages that target the managed environment, you as a developer are constrained to defining the type of any object you need to work with.

OK, but what if you want to store data in a typesafe collection sans any boxing and unboxing overhead? Here's where generics come to the rescue.

Generics enable you to work with typesafe data with no boxing and unboxing overhead. You can leverage generics to create typesafe collections, as well as classes and methods that can accept a type as a parameter. Such classes once declared can work with any type. This helps your code to be much more manageable, extensible, maintainable, and efficient. In this article we would explore generics and how they can be implemented using the C# programming language.

Generics help you implement algorithms that can work on a wide variety of types. As an example, you may want to write an algorithm to sort an array of integers or doubles or even an array of strings. To implement such sorting algorithms without generics you would typically need multiple sort methods -- one per each type.

Once you have declared a class or a method using type parameters, you can defer specifying the type that the classes or methods would work with till the time these classes and methods are accessed from the client code. Generics promotes type safety, code maintenance, code efficiency, and improved performance. Note that you can leverage generics to implement your own generic classes, interfaces, methods, events, and delegates.

When using generics, you no longer need to type cast the objects to the respective types -- the type information is well documented in your code. When you use generics, the compiler makes compile time checks on your code for conformance to type safety. Code that makes use of generics is always due to the avoidance of boxing and unboxing overheads.

The following code listing shows three methods that sort the input data -- you need one sort method for each type.

public static int[] Sort(int[] integerArray)

  {

      //Code to sort an array of integers

      return integerArray;

  }

public static string[] Sort(string[] stringArray)

  {

      //Code to sort an array of strings

      return stringArray;

  }

public double[] Sort(double[] doubleArray)

  {

      //Code to sort an array of doubles

      return doubleArray;

  }

If you use Generics, you can just have one method that can accept a type parameter and sort the input data when asked.

public class Algorithm<T>

    {

        public static T[] Sort(T[] inputArray)

        {

            //Code to sort a generic array

            return inputArray;

        }

    }

Type parameter constraints

When working with generics you should be aware of the generic constraints that include: derivation constraints and default constructor constraints. The derivation constraints are used to specify the interface or class that would be used to define the derivative for the generic type.

Here is an example that illustrates how the interface ILogger has been used to restrict the type parameter T (when defining the DBLogger class) to be of the type ILogger interface.

public interface ILogger

    {

        //Some code

    }

    public class DBLogger<T> where T : ILogger

    {

        //Some code

    }

Your generic type parameters can be either be of value or reference types. As an example, you can define your class that contains a generic type parameter in any of the following ways.

public class FileLogger<T> where T : class

    {

        //Some code

    }

 public class BaseLogger <T> where T: int

    {

        //Some code

    }

The constructor constraint is used to enforce a default constructor for the generic type parameter. Here is an example to illustrate this concept.

class DBLogger<T> where T : new() // The generic type parameter T must have a default constructor

{

   //Some code

}

You can also use generic type parameters when working with inheritance. Here is an example that shows how you can achieve this. Note that the generic type parameter in the example given next should have a default constructor.

public class BaseLogger <T>

    {

        //Some code

    }

    public class FileLogger<T> : BaseLogger<T> where T : new()

    {

        //Some code

    }

The following code listing shows how generic methods can be implemented.

public class BaseLogger <T>

    {

        public void Initialize(T t)

        {

          //Code to initialize logger

        }

    }

Refer to the code snippet given above. Note how the generic type parameter has been used as a parameter in the Initialize() method. I would discuss more on generics in my future posts here. You can learn more about generics here: https://msdn.microsoft.com/en-us/library/512aeb7t.aspx

This article is published as part of the IDG Contributor Network. Want to Join?

To comment on this article and other InfoWorld content, visit InfoWorld's LinkedIn page, Facebook page and Twitter stream.
From CIO: 8 Free Online Courses to Grow Your Tech Skills
Notice to our Readers
We're now using social media to take your comments and feedback. Learn more about this here.