Friday, May 1, 2015

C# - Generics

Generics are the most powerful feature of C# 2.0. Generics allow you to define type-safe data structures, without committing to actual data types. This results in a significant performance boost and higher quality code, because you get to reuse data processing algorithms without duplicating type-specific code. 
Generics provide a facility for creating data structures that are specialized to handle specific types when declaring a variable. Programmers define these parameterized types so that each variable of a particular generic type has the same internal algorithm but the types of data and method signatures can vary based on programmer preference.


Creating Generic List<T> Collections
The pattern for using a generic List collection is similar to arrays. We declare the List, populate its members, then access the members. Here's a code example of how to use a List:












The first thing we should notice is the generic collection List<int>, which is referred to as List of int. If you looked in the documentation for this class, we would find that it is defined as List<T>, where T could be any type. For example, if you wanted the list to work on string or Customer objects, we could define them as List<string> or List<Customer> and they would hold only string or Customer objects. In the example above, myInts holds only type int.

Using the Add method, you can add as many int objects to the collection as you want. This is different from arrays, which have a fixed size. The List<T> class has many more methods we can use, such as Contains, Remove, and more.
There are two parts of the for loop that you need to know about. First, the condition uses the Count property of myInts. This is another difference between collections and arrays in that an array uses a Length property for the same thing. Next, the way to read from a specific position in the List<T> collection, myInts[i], is the exact same syntax you use with arrays.
The next time we start to use a single-dimension array, consider using a List<T> instead. That said, be sure to let our solution fit the problem and use the best tool for the job. i.e. it's common to work with byte[] in many places in the .NET Framework.

Working with Dictionary<TKey, TValue> Collections
Another very useful generic collection is the Dictionary, which works with key/value pairs. There is a non-generic collection, called a Hashtable that does the same thing, except that it operates on type object. However, as explained earlier in this lesson, we want to avoid the non-generic collections and use thier generic counterparts instead. The scenario I'll use for this example is that you have a list of Customers that you need to work with. It would be natural to keep track of these Customers via their CustomerID. The Dictionary example will work with instances of the following Customer class:









The customers variable is declared as a Dictionary<int, Customer>.  Considering that the formal declaration of Dictionary is Dictionary<TKey, TValue>, the meaning of customers is that it is a Dictionary where the key is type int and the value is type Customer. Therefore, any time you add an entry to the Dictionary, you must provide the key because it is also the key that you will use to extract a specified Customer from the Dictionary.

We created three Customer objects, giving each an ID and a Name. I'll use the ID as the key and the entire Customer object as the value. You can see this in the calls to Add, where custX.ID is added as the key (first parameter) and the custX instance is added as the value (second parameter).
Extracting information from a Dictionary is a little bit different. Iterating through the customers Dictionary with a foreach loop, the type returned is KeyValuePair<TKey, TValue>, where TKey is type int and TValue is type Customer because those are the types that the customers Dictionary is defined with.

Since custKeyVal is type KeyValuePair<int, Customer> it has Key and Value properties for you to read from. In our example, custKeyVal.Key will hold the ID for the Customer instance and custKeyVal.Value will hold the whole Customer instance. The parameters in the Console.WriteLine statement demonstrate this by printing out the ID, obtained through the Key property, and the Name, obtained through the Name property of the Customer instance that is returned by the Value property.

The Dictionary type is handy for those situations where you need to keep track of objects via some unique identifier. For your convenience, here's Listing 20-1, shows how both the List and Dictionary collections work.

Benefits of Generics
There are several advantages to using a generic class (such as the System.Collections.Generic.Stack<T> class used earlier instead of the original System.Collections.Stack type).
Generics facilitate a strongly typed programming model, preventing data types other than those explicitly intended by the members within the parameterized class.
  1. Compile-time type checking reduces the likelihood of InvalidCastException type errors at runtime.
  2. Using value types with generic class members no longer causes a cast to object; they no longer require a boxing operation. (For example, path.Pop() and path.Push() do not require an item to be boxed when added or unboxed when removed.)
  3. Generics in C# reduce code bloat. Generic types retain the benefits of specific class versions, without the overhead. (For example, it is no longer necessary to define a class such as CellStack.)
  4. Performance increases because casting from an object is no longer required, thus eliminating a type check operation. Also, performance increases because boxing is no longer necessary for value types.
  5. Generics reduce memory consumption because the avoidance of boxing no longer consumes memory on the heap.
  6. Code becomes more readable because of fewer casting checks and because of the need for fewer type-specific implementations.
  7. Editors that assist coding via some type of IntelliSense work directly with return parameters from generic classes. There is no need to cast the return data for IntelliSense to work.
At their core, generics offer the ability to code pattern implementations and then reuse those implementations wherever the patterns appear. Patterns describe problems that occur repeatedly within code, and templates provide a single implementation for these repeating patterns.

No comments:

Post a Comment