Generics are types with parameters, and parameters are placeholders for future types. Generic types include classes, structures, and interfaces. The essence of the type remains. The persona of a class, even a generic class, remains a class—it is simply a class with type parameters.
Parameters appear after the class header and are placed in anchor brackets (< and >). The type parameters are accessible to the class declaration, header, body, and constraints. Because collections are typically general-purpose, generics are ideal for implementing collections. .NET provides generic collections for arrays, stacks, queues, and other common data structures. However, there is no predefined spreadsheet collection, which is a collection of rows and columns. Sounds like an opportunity to me.
Here is sample code for a sheet collection:
using System; namespace Donis.CSharpBook{ public class Starter{ public static void Main(){ int count=1; Sheet<int> asheet=new Sheet<int>(2); for(byte row=1; row<3; ++row) { for(byte col=1; col<3; ++col) { asheet[row,col]=count++; } } for(byte row=1; row<3; ++row) { for(byte col=1; col<3; ++col) { Console.WriteLine("R{0} C{1}= {2}", row, col, asheet[row,col]); } } Console.WriteLine("Current[{0},{1}] = {2}", asheet.R, asheet.C, asheet.Current); asheet.MoveDown(); asheet.MoveRight(); Console.WriteLine("Current[{0},{1}] = {2}", asheet.R, asheet.C, asheet.Current); } } class Sheet<T> { public Sheet(byte dimension) { if(dimension<0) { throw new Exception("Invalid dimensions"); } m_Dimension=dimension; m_Sheet=new T[dimension, dimension]; for(byte row=0; row<dimension; ++row) { for(byte col=0; col<dimension; ++col) { m_Sheet[row,col]=default(T); } } } public T this[byte row, byte col] { get { ValidateCell(row, col); return m_Sheet[row-1, col-1]; } set { m_Sheet[row-1, col-1]=value; } } public void ValidateCell(byte row, byte col) { if((row < 0) || (row > m_Dimension)) { throw new Exception("Invalid Row"); } if((col < 0) || (col > m_Dimension)) { throw new Exception("Invalid Col"); } } public T Current { get { return m_Sheet[curRow-1, curCol-1]; } set { m_Sheet[curRow-1, curCol-1]=value; } } public void MoveLeft() { curCol=Math.Max((byte) (curCol-1), (byte) 1); } public void MoveRight() { curCol=Math.Min((byte) (curCol+1), (byte) m_Dimension); } public void MoveUp() { curRow=Math.Max((byte) (curRow-1), (byte) 1); } public void MoveDown() { curRow=Math.Min((byte) (curRow+1), (byte) m_Dimension); } private byte curRow=1; public byte R { get { return curRow; } } private byte curCol=1; public byte C { get { return curCol; } } private byte m_Dimension; private T [,] m_Sheet; } }
Sheet is a generic type and collection, with a single parameter. For generics with a single parameter, by convention, T is the name of the parameter. (T is for type.) In the sheet generic type, the type parameter is used as a function return, field type, and local variable. When the sheet is instantiated, the specific type is specified. This defines a spreadsheet of strings, which has two rows and columns:
Sheet<string> asheet=new Sheet<string>(2);
Review the constructor of the sheet generic type. Notice the use of the default keyword:
for(byte row=0; row<dimension; ++row) { for(byte col=0; col<dimension; ++col) { m_Sheet[row,col]=default(T); } }
The preceding for loop initializes the internal collection of the Sheet generic type. However, the implementation is not based on a specific type. A generic type implementation has placeholders instead of specific types. The challenge is initializing a nonspecific type, which could be a reference or value type. The default expression returns null for a reference type and bitwise zero for a value type. This is the syntax of the default expression:
zerovalue default(type)
The Sheet generic type has a single parameter. Generics can have multiple parameters. The type parameter list is contained within the anchor brackets. Parameters in the type parameter list are comma-delimited. If a generic has two parameters, the parameters are normally named K and V, for key and value. Here is sample code of a generic type that has two parameters:
public class ZClass<K, V> { static void Method(K key, V data) { } }
This code creates an instance of the ZClass, which has two parameters:
ZClass<string, float> obj=new ZClass<string, float>();
Generic types have parameters. Those parameters can themselves be generic types. In the following code, XClass is generic and is also inserted as a nested parameter:
using System; namespace Donis.CSharpBook{ public class Starter{ public static void Main(){ ZClass<XParameter<string>, float> obj= new ZClass<XParameter<string>, float>(); } } public class XParameter<P> { public static void MethodA(P data) { } } public class ZClass<K, V> { public static void MethodB(K key, V data) { } } }
The syntax of nested parameters can be somewhat complicated. What if the type parameter was extrapolated even further? You could have a type parameter that is a generic, which has a type parameter that is also generic, and so on, which could become somewhat labyrinthine. If the declaration is repeated, it would be particularly problematic. An alias is a good solution. Alias the declaration containing a nested parameter and rely on the alias in the code. The following alias is used for this purpose:
using ZClass2=ZClass<XParameter<string>, int>; public class Starter{ public static void Main(){ ZClass2 obj=new ZClass2(); } } public class XParameter<P> { public static void MethodA(P data) { } } public class ZClass<K, V> { public static void MethodB(K key, V data) { } }
This is the syntax of a generic type:
attributes accessibility modifiers class classname : baselist <parameterlist> where parameter:constraintlist: { class body };
Generic types are also called constructed types. There are open constructed and closed constructed types. An open constructed type has at least one type parameter. The type parameter is a placeholder, which is unbound to a specific type. The implementation of a generic type is an example of an open constructed type. This is an open constructed type:
public class ZClass<K, V> { static void Method(K key, V data) { } }
For a closed constructed type, all type parameters are bound. Bound parameters are called type arguments and are assigned a specific type. Closed constructed types are used in several circumstances: to create an instance of a generic type, inherit a generic type, and more. In the following code, ZClass<int, decimal> is a closed constructed type. The first parameter is bound to an integer, whereas the second is a decimal. The type arguments are int and decimal.
ZClass<int, decimal> obj=new ZClass<int, decimal>();