CLR Generics – Bibliography
CLR Generics – Comparing with other generics implementations
CLR Generics – Notable BCL Additions
Generic based collection | Non Generic equivalent |
---|---|
List | ArrayList |
Dictionary | Hashtable |
SortedDictionary | SortedList |
Stack | Stack |
Queue | Queue |
LinkedList | – |
IList | IList |
IDictionary | IDictionary |
ICollection | ICollection |
IEnumerable | IEnumerable |
IEnumerator | IEnumerator |
IComparable | IComparable |
IComparer | IComparer |
System.Nullable introduces nullable value types to CLR. You cannot assign value types like int, long, float, double etc to nulls in 1.1. This would be a pain especially if you are reading a nullable integer field from the database.
CLR Generics – Internals
instance void Add(!T item) cil managed
CLR Generics – Benefits and limitations
- Code reuse
- Compile time type checking
- Performance benefits by avoiding boxing, unboxing for value types and also type compatibility checks involved with casts are needed for value types as well as reference types
A serious limitation of CLR Generics
Generic type variables cannot use operators. Isn’t it a serious one. A few workarounds http://blogs.msdn.com/ericgu/archive/2003/11/14/52852.aspx
http://www.codeproject.com/csharp/genericnumerics.asp
CLR Generics – Other interesting trivia
Inititalizing a generic to a default is not possible in case of value types. In case of reference types it is as easy as "T myVar=null;". In case of value types this is illegal. For value types the C# language has been extended using a new keyword default. "T myVar = default(T);"
Generic Interfaces, Delegates, Methods: In addition to Generic classes there can be Generic interfaces, Generic delegates and Generic methods. Let us just see an example of each.
public interface IEnumerator : IDisposable, IEnumerator { T Current { get; } } public delegate void EventHandler ( object sender, TEventArgs e) where TEventArgs : EventArgs; public static class Interlocked { public static T Exchange( ref T location1, T value) where T : class { ... } // Other members and implementation elided for clarity }
Properties, Indexers, Events, Operators, Constructors, Finalizers cannot have generic type parameters on their own like the Generic method Exchange defined above with a type parameter T.
CLR Generics – Constraints
Generic type parameters can be constrained. Assume we want to write an encryption algorithm using a generic type.
public class EncryptionHelper { ... }
Here we want all the type parameters to inherit from the System.IO.Stream class. How can this be done? Generic type parameters can be constrained. Assume we want to write an encryption algorithm using a generic type.
public class EncryptionHelper { public T Encrypt(T stream) { stream.Open(); // Error } }
Error object class doesn’t have a Open method. Can this be a solution?
public class EncryptionHelper { public T Encrypt(T stream) { Stream streamType = (Stream)T; // Error ... } }
This will work.
public class EncryptionHelper { public T Encrypt(T stream) { Stream streamType = (Stream)((Object)T); streamType.Open(); ... } }
But what about the type safety which generics promised? The client can pass instance of any class to this method. The actual solution is to use constraints.
public class EncryptionHelper where T:System.IO.Stream { public T Encrypt(T stream) { stream.Open(); // Error } }
The where clause is used to specify a constraint that the type parameter has to inherit from Stream class. The compiler can check for these calls. This is called a primary constraint. There are two special primary consraints ‘struct’, ‘class’ which specify whether the type argument has to be a value type or reference type. The primary constraint can specify at the most one non sealed type. This type cannot be an enum or a delegate. In addition using a constraint you also specify a zero or more interfaces a type parameter has to comply with. This is called a secondary constraint. In addition to primary and seondary constraints you can specify a type to have a default parameterless constructor. This is called a constructor constraint, specified like "…where T : new() …".
CLR Generics – Terminology
[serializable] public class List : IList, ICollection, IEnumerable, IList, ICollection, IEnumerable { public void Add(T item); public T[] ToArray(); // ... rest of the code elided for clarity } // A Client of List public class Program { public static void Main(string[] args) { List list = new List(); list.Add(1); }
Terms
Generic Type:
A generic type is a type which has one or more parameters (called type parameters). In the example List is a Generic type because it has one parameter (called type parameter) T.
Generic Type Parameter:
A generic type parameter is a place holder type used by generic types. In the List example above T is the placeholder type. Hence T is the generic type parameter.
Arity:
Arity is the number of type parameters a generic type has. In the List example above the arity of the List type is 1, since T is the one and only type parameter.
public class DictionaryEntry ...
The arity of DictionaryEntry is 2 since it has two type parameters.
Generic Type Argument:
A generic type argument is the type provided by the client while declaration / instantion. In the above example int is the generic type argument.
Constructed Type:
A constructed type is a usage (declaration) of the generic type in which the client has specified at least on of the type parameters as a type argument. A type argument need not be a concrete (non generic / generic type to which all the type parameters are specified – do not confuse this with abstract / concrete used to describe inheritance relationships) type like int as in this example. It can also be a type parameter. We’ll see an example of this in the definition of a open constructed type
Closed Constructed Type:
A closed constructed type is one to which all the type parameters are specified as type arguments using concrete types. In the above example, List is a closed constructed type because it has its only type parameter T specified as a type argument int.
Open Constructed Type:
A Open constructed type is one to which atleast one of the type parameters is specified in terms of another type parameter. This is slightly confusing. Let us see another example here.
public class DictionaryEntry ... // An dictionary entry which has integer keys public class IntegerKeyBasedDictionayEntry { private inetegerKeyBasedEntry DictionaryEntry; ... }
Here only one of the two type parameters specified is concrete, int. The other is specified in terms of the type parameter TValue itself. This is a open constructed type.
Whew so many new terms, well it is needed if you need to understand any article written on generics.
CLR Generics – Enter generics
List list = new List(); 1. list.Add(3); 2. list.Add(1); 3. list.Add(5); list.Sort(); 4. int oddNumber = list[0];
The important difference here is that lines labeled 1, 2 and 3 do not result in boxing operations. Line labeled 4 doesn’t require a unbox, cast and type compatibility check.
The reference type example written using the Generic collections in the BCL.
List list = new List(); 1. list.Add(aCustomerObject); 2. list.Add(anotherCustomerObject); ... // This would result in a compile error 3. list.Add(anOrderObject); ... 4. aListControl.DataSource = list 5. aListControl.DataBind();
Line labeled 3 results in a compile time error. This is the type safety offered by the strongly typed generic collections library of the BCL. Its time to introduce some terms in the CLR Generics world. That’ll be in the next entry, till then bye.
CLR Generics – Motivation
ArrayList list = new ArrayList(); 1. list.Add(3); 2. list.Add(1); 3. list.Add(5); list.Sort(); 4. int oddNumber = (int)list[0];
lines labeled 1, 2, 3 result in a boxing instruction. Because ArrayList.Add takes an object. Since 3, 1, 5 are integers (read value types) they will be boxed first. Likewise line labeled 4 notice results in an unbox operation because the indexer returns an object. Also cast operation requires a type compatibility check too.
A Reference Types example:
ArrayList list = new ArrayList(); 1. list.Add(aCustomerObject); 2. list.Add(anotherCustomerObject); ... 3. list.Add(anOrderObject); ... // Oops what happens now? 4. aListControl.DataSource = list 5. aListControl.DataBind();
Code duplication:
The only way one could work around this problem in a pre generic world is by writing strongly typed collections deriving from CollectionBase or directly implementing IList. This class has to written one for each type for ex. CustomerCollection, OrderCollection. This is unneccesary code duplication. But you had to live with it if you were to get the benefits of compile time type checking.