KodeBLOG

Kode Blog - Inspiring And Empowering Developers.

Home About Us Courses Categories Blog Contact Us

Generics In C#

Introduction

Generics in C# are some of the most powerful features of C# and they are also one of the least understood topics. You may have heard of generics in C# before but like many others, you just do not see the point of using them. You may be new to generics in C# and you are not so sure what they can do for you. This tutorial will take a different approach from the other tutorials.

Topics to be covered

  • Generics in C#: The problem statement
  • What are Generics?
    • Advantages of Generics in C#
  • C# Generic Method
  • C# Generic Class
  • C# Generic List
  • C# Generic Constraints

Generics in C#: The problem statement

We are going to explain this with the aid of an example. Let's suppose that you want to compare two strings to determine if they are equal. You can use the following code to do that.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CSharpGenerics
{
    class Program
    {
        static void Main(string[] args)
        {
            string value = IsEqual("a", "a") ? "Equal" : "Not Equal";

            Console.WriteLine(value);
            Console.ReadLine();
        }

        static bool IsEqual(string x, string y)
        {
            return x.Equals(y);
        }
    }
}

HERE,

static bool IsEqual(string x, string y)
{
    return x.Equals(y);
}

The above method accepts two string parameters and returns a boolean value of true if they are equal and false if they are not equal.

Let's say you want to compare int data types. You can create another method similar to the following

static bool IsEqual(int x, int y)
{
    return x.Equals(y);
}

If your requirements demand you do the same for double, long, and other data types, you can end up with a lot of methods that do the same thing. Let's consider the following alternative that allows us to work with one method only and be able to pass in different parameters.

static bool IsEqual(object x, object y)
{
    return x.Equals(y);
}

HERE,

The above method uses object as the type for the parameters. Object can accept any data type. If you compile and run the object with the following parameters

string value = IsEqual(1, "1") ? "Equal" : "Not Equal";

The above code builds successfully and produces the following results.

Not Equal

You got the results that you wanted right? so what could be wrong with the above code?

  • The code is prune to runtime error. It allows you to compare oranges and apples - as you can see from the parameter values passed in, the first parameter is an int while the second parameter is a string. For our example, this is not a big deal but think of a situation where you are working with objects that have different properties. If you pass in a car and person object, the compiler wont detect any errors but at runtime, you will get an exception if you try to access a property or method that does not exist. We will demonstrate this later on in this tutorial.
  • Performance Degradation due to boxing - Boxing is the process of converting a value type to the type of object. Unboxing is the process of converting an object type back to a value type. int data type is a struct therefore its a value type. Enumerations are also value types. This means passing in an int as a parameter value will involve boxing and unboxing.

What are Generics in C#?

Generics refers to features of C# that allow you to declare methods, class, interfaces etc. independent of the data type that the particular method, class, interface etc. stores or operates on it. We will illustrate this with the aid of an example shortly. Below is a list of advantages of Generics in C#.

Advantages of Generics in C#

  1. Type safety - this ensures that the method, class, or interface etc. works only on the correct data type. For example, if you use string when calling a generic method, then all the values passed in as expected to be strings. If you pass in an int value parameter, the compiler will detect that as an error and the program will not be able to compile.
  2. Errors/Exceptions are caught at compile time as opposed to runtime errors/exceptions - if given a choice between compile time and runtime errors, 100% of developers choose compile time errors so should you. The last thing we want is errors crashing the program when the users are working with it.
  3. Re-usability & Flexibility - in the above example on the problem statement, we declared more than one method and the only difference was the data type of the parameters that we passed. With generics, you only have to create one method and re-use it with any data type that you want
  4. Write clean code - we do not need to write methods, class or interfaces that contain complex logic that checks the data type of the parameter values passed in.

C# Generic Method

We will now revisit the problem we tried to solve in the problem statement section. The following code is a generic method implementation of IsEqual method.

static bool IsEqual<T>(T x, T y)
{
    return x.Equals(y);
}

HERE,

static bool IsEqual<T>(...)

The above code defines a method IsEqual of data type T. T is the place holder for the actual data type that will be determined when calling the method.

(T x, T y)

The above code defines two parameters of type T. T is defined when calling the method. If you pass in string as the type for T, then the data type for x and y parameters will be string, if you pass in an int, then the data type will be an int and so on and so fourth.

The complete application source code is as follows. Note: the code also includes the methods that we defined in the problem statement section

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CSharpGenerics
{
    class Program
    {
        static void Main(string[] args)
        {
            //problem statement
            string value = IsEqual(1, "1") ? "Equal" : "Not Equal";

            Console.WriteLine("Non Generic Method string " + value);

            //generic method solution
            string genericValue = IsEqual<string>("1", "1") ? "Equal" : "Not Equal";
            Console.WriteLine("Generic Method string " + genericValue);

            string genericValue2 = IsEqual<int>(1, 2) ? "Equal" : "Not Equal";
            Console.WriteLine("Generic Method int " + genericValue2);

            Console.ReadLine();
        }

        static bool IsEqual(string x, string y)
        {
            return x.Equals(y);
        }

        static bool IsEqual(int x, int y)
        {
            return x.Equals(y);
        }

        static bool IsEqual(object x, object y)
        {
            return x.Equals(y);
        }

        static bool IsEqual<T>(T x, T y)
        {
            return x.Equals(y);
        }
    }
}

Run the program. You will get the following output

Non Generic Method string Not Equal
Generic Method string Equal
Generic Method int Not Equal

C# Generic Class

C# Generic classes are similar to Generic methods. The major difference is, the type is set on the class itself and not the methods inside the class. Let's illustrate this with the aid of an example. Let's say we want to create another method NotEqual, we can create a comparison class of type T and add both IsEqual and NotEqual methods in it.

The following code demonstrates how to do this.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CSharpGenerics
{
    public static class ComparisonUtils<T>
    {
        public static bool IsEqual(T x, T y)
        {
            return x.Equals(y);
        }

        public static bool NotEqual(T x, T y)
        {
            return !x.Equals(y);
        }
    }
}

HERE,

public static class ComparisonUtils<T>

The above code defines a class ComaprisonUtils of type T. The type for T is set when calling the class.

public static bool IsEqual(T x, T y)

The methods IsEqual, and NotEqual accept parameters of type T.

The following code demonstrates how to call the NotEqual method

string genericValue3 = ComparisonUtils<int>.NotEqual(122, 122) ? "Not Equal" : "Equal";
Console.WriteLine("Generic Class double " + genericValue3);

The above code produces the following results

Generic Class double Equal

C# Generic List

List<T> Class is a strongly typed list of objects that can be accessed by index. Provides methods to search, sort, and manipulate lists. List is implemented in the System.Collections.Generic namespace. As you guessed correct, List is generic and allows us to define the type when initializing the class.

Let's now get our hands dirty.

  1. Create a new class Snack.cs
  2. Create a new class Beverage.cs
  3. Create a new class ItemsStore.cs

Add the following code to Snack.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CSharpGenerics
{
    class Snack
    {
        public string snack_id { get; set; }
        public string snack_name { get; set; }
        public double snack_price { get; set; }
    }
}

HERE,

  • The above is a simple class that defines three properties namely; snack_id, snack_name and snack_price. It also defines get and set properties on the fields.

Add the following code to Beverage.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CSharpGenerics
{
    class Beverage
    {
        public string beverage_id { get; set; }
        public string beverage_name { get; set; }
        public double beverage_price { get; set; }
    }
}

Add the following code to ItemsStore.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CSharpGenerics
{
    class ItemsStore
    {
        public static List<Snack> GetSnacks()
        {
            var snack = new List<Snack&gt()
            {
                new Snack {snack_id="1",snack_name="Piza",snack_price=120},
                new Snack {snack_id="2",snack_name="Jello",snack_price = 20}
            };

            return snack;
        }

        public static List<Beverage> GetBeverages()
        {
            var beverage = new List<Beverage>()
            {
                new Beverage {beverage_id="1",beverage_name="Coke",beverage_price=15},
                new Beverage {beverage_id="2",beverage_name="Milkshake laced with 1% Gin and 0.5% Vodka",beverage_price = 20}
            };

            return beverage;
        }
    }
}

HERE,

public static List<Snack> GetSnacks(){...}

defines a static public method that returns a list of type Snack. This ensures our list only contains items of type Snack. If you try to pass in a beverage type, then you will get a compiler error. This is the advantage of using List<T> class.

The following code demonstrates how to use the generic list

//generic list (snacks)
List<Snack> snacks = ItemsStore.GetSnacks();

Console.WriteLine("Snack Items Listing");

foreach (Snack snack in snacks)
{ 
    Console.WriteLine("snack id: " + snack.snack_id + " name: " + snack.snack_name + " price: " + snack.snack_price);
}

Console.WriteLine();

//generic list (beverages)
List<Beverage> beverages = ItemsStore.GetBeverages();

Console.WriteLine("Beverages Items Listing");

foreach (Beverage beverage in beverages)
{
    Console.WriteLine("beverage id: " + beverage.beverage_id + " name: " + beverage.beverage_name + " price: " + beverage.beverage_price);
}

Console.ReadLine();

The above code produces the following results.

Snack Items Listing
snack id: 1 name: Pizza price: 120
snack id: 2 name: Jello price: 20

Beverages Items Listing
beverage id: 1 name: Coke price: 120
beverage id: 2 name: Milkshake laced with 1% Gin and 0.5% Vodka price: 20

Summary

  • Generics in C# are used to enforce type safety
  • Generics promote code re-use
  • Generics allow us to catch type errors at compile time rather than at runtime
  • Generics can be applied to methods, classes, lists, and interfaces etc.

Tutorial History

Tutorial version 1: Date Published 2015-08-21