Introduction
Developers like you are familiar with object-oriented programming and understand the benefits it offers. One of the big benefits of object-oriented programming is code re-use, in which you create a base class and inherit it in a derived class. The derived class can simply override virtual methods or add some new methods to customize the behavior of the base class to meet yours need. The Generics is another mechanism offered by the Common Language Runtime (CLR) and programming languages that provide one more form of code re-use and algorithm re-use.
Generics provide a code template for creating type-safe code without referring to specific data types. Generics allow you to realize type safety at compile time. They allow you to create a data structure without committing to a specific data type. When the data structure is used, however, the compiler ensures that the types used with it are consistent for type safety. I want to discuss two benefits of generics, one is type safety and another is performance before explaining with an example.
Type Safety
Generics are mostly used for collections but the framework class library also has non-generic collection classes like ArrayList
, Hashtable
, SortedList
, Stack
, Queue
. So, first of all, we see an example of a non-generic collection class. In other words, ArrayList
where we add integer values to the array list and perform addition operation on the list values.
You need to create a class, ArrayListOperation
, and define an Addition
method as in the following code snippet.
using System.Collections;
namespace TypeSafety
{
class ArrayListOperation
{
public static int Addition()
{
ArrayList list = new ArrayList();
list.Add(5);
list.Add(9);
int result = 0;
foreach (int value in list)
{
result += value;
}
return result;
}
}
}
When you run this code you get a return value from the method.
As you saw in the previous example there is an array list of integer values and get the result as expected but now add one more value to the ArrayList
that the data type is float and perform the same Addition
operation. Let's see the updated code in the following snippet.
using System.Collections;
namespace TypeSafety
{
class ArrayListOperation
{
public static int Addition()
{
ArrayList list = new ArrayList();
list.Add(5);
list.Add(9);
list.Add(5.10);
int result = 0;
foreach (int value in list)
{
result += value;
}
return result;
}
}
}
In the code above, all three values are easily added to the array list because the ArrayList
class Add()
method values are an object type, but when retrieving the values using each statement, each value will be assigned an int
data type variable. However, this array list has a combination of both integer and float type values and float values won't cast to an int implicitly. That is why the code will give the exception "Specified cast is not valid." That means that an ArrayList
is not type safe. This also means that an ArrayList
can be assigned a value of any type.
Generics allow you to realize type safety at compile time. They allow you to create a data structure without committing to a specific data type. When the data structure is used, however, the compiler ensures that the types used with it are consistent for type safety. Generics provide type safety, but without any loss of performance or code bloat. The System.Collections.Generics
namespace contains the generics collections. Now let's see an example with a generic collection List.
using System.Collections.Generic;
namespace TypeSafety
{
class ListOperation
{
public static int Addition()
{
List<int> list = new List<int>();
list.Add(5);
list.Add(9);
int result = 0;
foreach (int value in list)
{
result += value;
}
return result;
}
}
}</int></int>
In the code above we define a list int type. In other words, we can only add an integer value to the list and can't add a float value to it so when we retrieve values from the list using a foreach
statement we only get an int value. When you run this code you will get a return value from the method. Now you can say that a generic collection is type safe.
Performance
Before discussing performance we need to understand object data types in C#. So what are object data types? From MSDN: "The object type is an alias for Object in the .NET Framework. In the unified type system of C#, all types, predefined and user-defined, reference types and value types, inherit directly or indirectly from Object. You can assign values of any type to variables of the type object. When a variable of a value type is converted to an object, it is said to be boxed.
When a variable of type object is converted to a value type, it is said to be unboxed."
I hope that makes sense.
Before generics, the way to define a generalized algorithm was to define its entire member to the Object data type. If you wanted to use the algorithm with value type instances, the CLR needed to box the value type instance prior to calling the members of the algorithm. This boxing causes memory allocations on the managed heap, that causes more frequent garbage collections, which, in turn, hurt an application's performance.
Since a generic algorithm can now be created to work with a specific value type, the instances of the value type can be passed by value, and the CLR no longer must do any boxing. In addition, since casts are not necessary, the CLR doesn't need to check the type safety of the attempted cast, and this results in faster code too.
Now let's see the syntax of the Add()
method for non-generic and generic collection classes. We are discussing two collection classes, one for non-generic (ArrayList) and another is generic (List).
The syntax of the Add()
method of ArrayList:
public virtual int Add(object value);
The preceding line of method signature represents that each value to be added to an ArrayList
will be an object type, in other words if you are using a value type to be added to an ArrayList
then it will be cast yo an object type before being added to ArrayList
.
The syntax of the Add()
method of List<T>
:
public void Add(T item);
The preceding line of method signature represents that there is no need to do a cast to add an item to a generic type list. If the List type is T
then the item will be T type.
Now you understand who has better performance. A generic collection class has better performance than a non-generic collection class. Let's see an example in the following code snippet.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
namespace Performance
{
class Program
{
static void Main(string[] args)
{
NonGenericPerformance();
GenericPerformance();
Console.ReadKey();
}
static void NonGenericPerformance()
{
long operationTime = 0;
ArrayList arraylist = new ArrayList();
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 1; i <= 100000; i++)
{
arraylist.Add(i);
}
operationTime = sw.ElapsedMilliseconds;
Console.WriteLine("Array List {0} values add time is {1} milliseconds", arraylist.Count, operationTime);
sw.Restart();
foreach (int i in arraylist)
{
int value = i;
}
operationTime = sw.ElapsedMilliseconds;
Console.WriteLine("Array List {0} values retrieve time is {1} milliseconds", arraylist.Count, operationTime);
}
static void GenericPerformance()
{
long operationTime = 0;
List<int> list = new List<int>();
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 1; i <= 100000; i++)
{
list.Add(i);
}
operationTime = sw.ElapsedMilliseconds;
Console.WriteLine("List {0} values add time is {1} milliseconds", list.Count, operationTime);
sw.Restart();
foreach (int i in list)
{
int value = i;
}
operationTime = sw.ElapsedMilliseconds;
Console.WriteLine("List {0} values retrieve time is {1} milliseconds", list.Count, operationTime);
}
}
}
Let's run the code above. The results are as in Figure 1.1.
Figure 1.1 Performance between generic and non generic collection.
How to reuse code using generics?
Here I will introduce a simple concept of code reuse using generics. I will populate UI lists (Dropdown, Checkbox, RadioButton) using a common method. So let's see the code.
First of all create a table Master that has the three fields Id, Title and Type (UI entity type). Let's see the database script for table creation and database insertion.
Create Table Master
(
Id int identity(1,1) primary key,
Title nvarchar(50) not null,
Type int not null
)
Insert Into Master values ('Jaipur',1),('Jhunjhunu',1),
('Cricket',2),('Football',2),('Male',3),('Female',3)
Now create an enum for the types on the UI as per our form design requirements.
namespace GenericUIList
{
public enum Types
{
NativePlace = 1,
Hobby = 2,
Gender = 3
}
}
Here the enum attribute values are the same as the Type field values in the database table Master.
Write the connection string in code behind, like:
<connectionStrings>
<add name ="genericConn" connectionString="Data Source=SANDEEPSS-PC;database=Development;user=sa;password=******"/>
</connectionStrings>
Now design a form that has three lists for NativePlace (Dropdownlist
), Hobbies
(Checkbox list) and Gender
(Radio button list) as in the following code snippet.
<p>Native Place : <asp:DropDownList ID="ddlNativePlace" runat="server"></asp:DropDownList></p>
<p>Hobbies : <asp:CheckBoxList ID="chkHobbies" runat="server"></asp:CheckBoxList></p>
<p>Gender : <asp:RadioButtonList ID="rbGender" runat="server"></asp:RadioButtonList></p>
Now we create a generic method in the code behind file and call that method on page load. Let's see the following code snippet on the .cs page of the web form.
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Configuration;
using System.Data.SqlClient;
using System.Data;
namespace GenericUIList
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
PopulateUIList<DropDownList>(ddlNativePlace, Types.NativePlace);
PopulateUIList<CheckBoxList>(chkHobbies, Types.Hobby);
PopulateUIList<RadioButtonList>(rbGender, Types.Gender);
}
}
public void PopulateUIList<T>(T list, Types type) where T : ListControl
{
string connectionString = ConfigurationManager.ConnectionStrings["genericConn"].ConnectionString;
using (SqlConnection con = new SqlConnection(connectionString))
{
if(con.State == ConnectionState.Closed)
{
con.Open();
}
string cmdText = "Select Id,Title from Master Where Type = @type";
using (SqlCommand cmd = new SqlCommand(cmdText, con))
{
cmd.Parameters.Add(new SqlParameter("@type",(int)type));
DataTable dt = new DataTable();
IDataReader dr = cmd.ExecuteReader();
dt.Load(dr);
list.DataSource = dt;
list.DataTextField = "Title";
list.DataValueField = "Id";
list.SelectedIndex = 0;
list.DataBind();
}
}
}
}
}
Run the application and get the results on the page as in Figure 1.2.
Figure 1.2 Populate UI List
Conclusion
I hope you now understand how generics are useful over non-generics in this article. We have also explained how to reuse code using generics. If you have any doubt then you can comment here and directly connect to me by https://twitter.com/ss_shekhawat.