Public Class Object Int Public Void Object Public Object: //unboxing With Explicit Int Casting
Public Class Object Int Public Void Object Public Object: //unboxing With Explicit Int Casting
The best way to understand generics is to study some C# code that would benefit from generics. The code stated below is about a simple Stack class with two methods: Push () and Pop (). First, without using generics example you can get a clear idea about two issues: a) Boxing and unboxing overhead and b) No strong type information at compile type. After that the same Stack class with the use of generics explains how these two issues are solved. Example Code:Code without using generics: public class Stack { object[] store; int size; public void Push(object x) {...} public object Pop() {...} } Boxing and unboxing overhead: You can push a value of any type onto a stack. To retrieve, the result of the Pop method must be explicitly cast back. For example if an integer passed to the Push method, it is automatically boxed. While retrieving, it must be unboxed with an explicit type cast. Stack stack = new Stack(); stack.Push(3); int i = (int)stack.Pop(); //unboxing with explicit int casting Such boxing and unboxing operations add performance overhead since they involve dynamic memory allocations and run-time type checks. No strong Type information at Compile Time Another issue with the Stack class: It is not possible to enforce the kind of data placed on a stack. For example, a string can be pushed on a stack and then accidentally cast to the wrong type like integer after it is retrieved: Stack stack = new Stack(); stack.Push("SomeName"); //pushing the string int i = (int)stack.Pop(); //run-time exception will be thrown at this point The above code is technically correct and you will not get any compile time error. The problem does not become visible until the code is executed; at that point an InvalidCastException is thrown. Code with generics
In C# with generics, you declare class Stack <T> {...}, where T is the type parameter. Within class Stack <T> you can use T as if it were a type. You can create a Stack as Integer by declaring Stack <int> or Stack as Customer object by declaring Stack<Customer>. Simply your type arguments get substituted for the type parameter. All of the Ts become ints or Customers, you don't have to downcast, and there is strong type checking everywhere. public class Stack<T> { // items are of type T, which is kown when you create the object T[] items; int count; public void Push(T item) {...} //type of method pop will be decided when you creat the object public T Pop() {...} } In the following example, int is given as the type argument for T: Stack<int> stack = new Stack<int>(); stack.Push(3); int i = stack.Pop(); The Stack<int> type is called a constructed type. In the Stack<int> type, every occurrence of T is replaced with the type argument int. The Push and Pop methods of a Stack<int> operate on int values, making it a compile-time error to push values of other types onto the stack, and eliminating the need to explicitly cast values back to their original type when they are retrieved. You can use parameterization not only for classes but also for interfaces, structs, methods and delegates. //For Interfaces interface IComparable <T> //for structs struct HashBucket <K,D> //for methods static void Reverse <T> (T[] arr) //for delegates delegate void Action <T> (T arg) Inside the CLR When you compile Stack<T>, or any other generic type, it compiles down to IL and metadata just like any normal type. The IL and metadata contains additional information that knows there's a type parameter. This means you have the type information at compile time. Implementation of parametric polymorphism can be done in two ways 1. Code Specialization: Specializing the code for each instantiation 2. Code sharing: Generating common code for all instantiations. The C# implementation of generics uses both code specialization and code sharing as explained below.
At runtime, when your application makes its first reference to Stack <int>, the system looks to see if anyone already asked for Stack <int>. If not, it feeds into the JIT the IL and metadata for Stack <T> and the type argument int. The .NET Common Language Runtime creates a specialized copy of the native code for each generic type instantiation with a value type, but shares a single copy of the native code for all reference types (since, at the native code level, references are just pointers with the same representation). In other words, for instantiations those are value types: such as Stack <int>, Stack <long>, Stack<double>, Stack<float> CLR creates a unique copy of the executable native code. So Stack<int> gets its own code. Stack<long> gets its own code. Stack <float> gets its own code. Stack <int> uses 32 bits and Stack <long> uses 64 bits. While reference types, Stack <dog> is different from Stack <cat>, but they actually share all the same method code and both are 32-bit pointers. This code sharing avoids code bloat and gives better performance. To support generics, Microsoft did some changes to CLR, metadata, typeloader,language compilers, IL instructions and so on for the next release of Visual Studio.NET(code named Whidbey). What you can get with Generics Generics can make the C# code more efficient, type-safe and maintainable. Efficiency: Following points states that how performance is boosted. 1. Instantiations of parameterized classes are loaded dynamically and the code for their methods is generated on demand [Just in Time]. 2. Where ever possible, compiled code and data representations are shared between different instantiations. 3. Due to type specialization, the implementation never needs to box values of primitive types. Safety: Strong type checking at compile time, hence more bugs caught at compile time itself. ArrayList Generics are new feature provided with version 2.0 of the Microsoft.Net framework. Generic classes and methods combine re-usability, type safety and efficiency in a way that there non-generics counterparts do not/cannot. In this part some features of ArrayLists and there shortcomings/limitations will be discussed. The code in this article has been written in Visual Studio 2005 in C#. Earlier ArrayLists used to server the purpose, but that in a certain limit. Moreover, using ArrayLists to store different types came as a good performance cost. At the client end it shows our desired type that we are storing in the ArrayList but internally there's much more that goes on. Sample the code below:-
System.Collections.ArrayList myList = new System.Collections.ArrayList (); myList.Add (22); myList.Add ("C# Generics"); myList.Add (22.45); The usage convenience that is perceived above comes at a cost. Any reference or value type that is stored or added in the ArrayList (myList) is implicitly upcasted to System.Object type. And while retrieval, the reverse happens - downcasting to the appropriate type takes place. Moreover, there is compromise on the Type Safety front also. Consider the code below:int item =0; //This will cause an InvalidCastException foreach (int x in myList) { Item = item+x; } The reason is evident. In .Net 1.x type-safety was achieved by writing your own typed ArrayList. But again, in that case re-usability was a major issue. Consider the following code:using using using using System; System.Collections; System.Collections.Generic; System.Text;
namespace ArrayListSample { #region Person Class class Person { String Name; Int32 Age; String Address; #region Constructor public Person() { } public Person(String Name, Int32 Age, String Address) { Name = Name; Age = Age; Address = Address; } public Person(String Name, Int32 Age) { Name = Name; Age = Age;
} #endregion
Address = String.Empty; } public Person(String Name, String Address) { Name = Name; Age = 0; Address = Address; } public Person(String Name) { Name = Name; Age = 0; Address = String.Empty; } #endregion
#region PeopleCollection Class class PeopleCollection : System.Collections.IEnumerable { private ArrayList arPeople = new ArrayList(); public PeopleCollection() { } #region Methods public void AddPeople(Person p) { arPeople.Add(p); } public void ClearPeople() { arPeople.Clear(); } int Count; public int PeopleCount { get { return Count; } } #endregion #region IEnumerable Members public System.Collections.IEnumerator GetEnumerator() { return arPeople.GetEnumerator(); }
#endregion } #endregion class Client { public static void Main() { PeopleCollection myPeople = new PeopleCollection(); myPeople.AddPeople(new Person("Saurabh",24,"Gurgaon")); myPeople.AddPeople(new Person("Manu")); foreach (Person Person in myPeople) { Console.WriteLine(Person); } Console.ReadLine(); } } The above code does achieve type safety but then we will have to write an almost identical custom collection for each type we wish to contain. Because:myPeople.AddPeople(new Car()); would be a compile time error, since the code/approach above achieves type safety. So at the end of the day it will be a big nightmare !! So above we find some of the limitations of ArrayLists. In the next article we will find how Generics solve the issues discussed above. In my last article "Limitations of ArrayLists in C#", I focused on issues of type safety and reusability when using ArrayLists. In this article we shall focus on how these issues of type safety and reusability are very nicely handled by Generics. All with the help of code again using System; using System.Collections.Generic; using System.Text; namespace GenericsSample { class Person { int _Age; public int Age { get { return _Age; } }
set { _Age = value; } } String _Name; public String Name { get { return _Name; } set { _Name = value; } } String _Address; public String Address { get { return _Address; } set { _Address = value; } } String _Company; public String Company { get { return _Company; } set { _Company = value; } } public Person() { } public Person(String Name) { this.Name = Name; this.Age = 0; this.Address = String.Empty; this.Company = String.Empty; } public Person(String Name, int Age, String Address) { this.Name = Name; this.Age = Age; this.Address = Address; } } class Program { static void Main(string[] args) { //Generic List Creation //List is a Generic Class provided by .Net Framework 2.0 //System.Collections.Generics is the Namespace. List<Person> myPerson = new List<Person>(); myPerson.Add(new Person("Saurabh")); myPerson.Add(new Person("Manu")); myPerson.Add(new Person("SomeOne", 24, "Gurgaon"));
myPerson.Add(new Person("SomeoneElse", 24, "Gurgaon")); //myPerson.Add(new Car());// This is A Compile Time Error foreach (Person p in myPerson) { Console.WriteLine(p.Name); Console.WriteLine(p.Age); Console.WriteLine(p.Address); Console.WriteLine(p.Company); } Console.ReadLine(); } } Two classes can be seen in this code. Class Person is the class of which we want to create list for, and class Program is the main class where we actually create the list of persons and operate upon them. How Generics tackle the issues posed by ArrayLists? In the above code example Generic List class has been used to "contain" objects of type Person. At any time we can we can have the Generic List contain any other type, as below://List of Ints List<int> myInts = new List<int>(); myInts.Add(5); myInts.Add(10); myInts.Add(20); foreach (int x in myInts) { Console.WriteLine(x); } Console.ReadLine(); The above code snippet indicates that the same List class can be used to contain any datatype at any point of time, without requiring any kind of extra effort from the programmer's side. The syntax for using any kind of Generic Class is as under :GenericClass<T> objT = new GenericClass<T>(); Where T is the datatype that' want to list, and GenericClass is the Generic Class which will wrap our desired datatype (that's the reason , "contains" ,above has been marked in the double quotes and is marked bold). This Generic Class can be our own custom Generic Class or the ones provided by the .Net Framework. }
So technically, T gets replaced by the datatype at compile type. And that's the reaosn why a compile time error occurs when castinig is not done properly, it will be an InvalidCast Exception while using ArrayLists. Thus Generics enforce type checking at complie time only, making life less difficult. Performance is another area where Generics make it sweet when compared to ArrayLists. Since T is "replaced" by our datatype at comile time only so, no time and resources are wasted in boxing and unboxing the objects. Thus Generics are a very powerful and nice feature provided with .Net 2.0. Generics types are found sprinkled throughout the .Net2.0 BCLs; however System.Collections.Generics namespace is chock full of them. Generic Types Generics are the most powerful feature of C# 2.0. It allows defining type-safe data structures, without committing to actual data types. In C# 1.0 we can either declare reference type or value type. But in most of the application we come across situation where we need type that can hold both reference & value type. In such situation we use generic types. Why Generics? 1. Generic type doesn't care what the type is. We can specify the type at runtime. 2. It avoids boxing difficulties. In C# 1.0, if we want to put any object into a List, Stack, or Queue objects, we have to take type as System.Object. 3. It boosts the performance of the application because we can reuse data processing algorithm without duplicating type-specific code. How Generic implemented: (1) Generic type is instantiated at run-time not compiled time (2) Generic type are checked at time of declaration not at instantiation (3) It works for both reference type & value type. Let's create simple class "GenericList" using C# 1.0 & 2.0 respectively & compare them. Code GenericList Class (C# 1.0) using System; using System.Collections.Generic; using System.Text; public class GenericList { private object[] elements; private int count; public GenericList() { elements = new object[10]; } public object this[int index] { get { return elements[index]; } set { elements[index] = value; } } public void Add (object parm)
} }
if (count == elements.Length) { // Increase the object[] tmpArray = null ; elements.CopyTo(tmpArray,0); elements = new object[count * 2]; elements = tmpArray; } elements[count] = parm; count = count + 1;
Main Method: static void Main(string[] args) { Console.WriteLine("using C# 1.0"); GenericList list = new GenericList(); list.Add(20); //Argument is boxed list.Add(40); //Argument is boxed list.Add("Sixty"); //Error in retrieving Console.WriteLine("Item Added"); int val = (int)list[0]; //Casting required Console.WriteLine("Value retrived : " + val); } Memory Consumption In C# 1.0 boxing is necessary evil to make type system work. While working with structures of System.Collection namespace (Stacks,List,Hashtable etc) we face the problem in insertion & retrieval of values. We need to take System.object as type & System.object is reference type, so whenever we access the hashtable, the runtime has to box the values to put into the collection & need to unbox to take it out. list.Add(20); //Argument is boxed In C# int takes 4 byte but when it boxed it take (4+8) 12 bytes, which is 3 times to normal size. In C# 2.0 the type is decided at runtime so boxing does not take place. Type Safe When we use the statement list.Add ("Sixty"); or List [3] = "sixty"; It compiles successfully but later on if some one pulls value and cast it into integer it fails. The problem is fixed in C# 2.0; we will get compilation error there. Code GenericList Class (C# 2.0) public class GenericList<T> { public GenericList() { elements = new T[10]; } private T[] elements; private int count;
public T this[int index] { get {return elements [index];} set {elements [index] = value;} } public void Add (T parm) { if (count == elements.Length) { T[] tmpArray = null; elements.CopyTo(tmpArray, 0); elements = new T [count * 2]; elements = tmpArray; } elements [count] = parm; count = count + 1; }
Main Method: static void Main(string[] args) { Console.WriteLine("using C# 1.0"); GenericList<int> genericList = new GenericList<int>(); genericList.Add (10); //No boxing genericList.Add (20); //No boxing // genericList.Add("Fifty"); //Compile Time Error Console.WriteLine("Item Added"); int valGeneric = (int)genericList[0]; //No Casting Required Console.WriteLine("Value retrived : " + valGeneric); } Some other Points: (1) Type parameter can be applied to Class, struct, interface & delegates. struct Buket<K, V>; interface ICompare<T> (2) Type parameter can have constraints. Constraint Description
Public class Books where T: struct The type argument for class Books must be a value type Public class Books where T: class The type argument for class Books must be a reference type Public class Books where T: new( ) The type argument for class Books must have a public default constructor
Public class Programmer where T: The type argument for class Programmer must be <Employee> of Employee type.