Generics
Generics can reduce casting and boxing and increase type safety.
Generic Types
A generic type declares type parameters—placeholder types to be filled in by the consumer of the generic type.
Stack<T> Example:
public class Stack{ int position; T[] data = new T[100]; public void Push (T obj) => data[position++] = obj; public T Pop() => data[--position]; } static void Main() { var stack = new Stack<int>(); stack.Push(5); stack.Push(10); int x = stack.Pop(); // x is 10 int y = stack.Pop(); // y is 5 }
Stack<T> fills the parameter type T will int, hence if you push string instead of int will generate error.
Stack<T> is an open type whereas Stack<int> is a closed type. During runtime all generic type are closed.
Why Generics Exist
Generics exist to write code that is reusable across different types. Without generic types, writing a general-purpose stack would require a solution.
public class ObjectStack { int position; object[] data = new object[10]; public void Push (object obj) => data[position++] = obj; public object Pop() => data[--position]; } static void Main() { // Now suppose we want a stack that stores just integers: ObjectStack stack = new ObjectStack(); // It's easy to make mistakes: stack.Push ("s"); // Wrong type, but no error! int i = (int)stack.Pop(); // Downcast - runtime error! }
Generic Methods
A generic method declares type parameters within the signature of a method.
static void Swap<T> (ref T a, ref T b) { T temp = a; a = b; b = temp; } static void Main() { int x = 5; int y = 10; Swap (ref x, ref y); }
Generally there is no need to supply type arguments to a generic method because the compiler can infer the type unless there is ambiguity then it can be called as :
Swap<int> (ref x, ref y);
A method is not classed as generic unless it has parameters(angle bracket syntax). Classes and methods can only define type parameter.
Declaring Type Parameters
Only classes, structs, interfaces, delegates and methods can define type parameters but others such as properties cannot introduce but can use one if defined.
struct Nullable<T> { public T Value { get; set; } }
A generic type or method can have multiple parameters.
class Dictionary <TKey, TValue> { /*...*/ } static void Main() { // To instantiate: Dictionary<int,string> myDic = new Dictionary<int,string>(); // Or: var myDicEasy = new Dictionary<int,string>(); }
Generic type names and method names can be overloaded as long as the number of type parameters is different.
class A { } class A<T> { } class A<T1,T2> { }
typeof and Unbound Generic Types
Since open generic types do not exit during runtime, but it is possible fdor an unbound generic type to exist at runtime as an Type object.
class A<T> {} class A<T1,T2> {} static void Main() { // The only way to specify an unbound generic type in C# is with the typeof operator: Type a1 = typeof (A< >); // Unbound type (notice no type arguments). Type a2 = typeof (A<,>); // Use commas to indicate multiple type args. // You can also use the typeof operator to specify a closed type: Type a3 = typeof (A<int,int>); } // or an open type (which is closed at runtime): class B<T> { void X() { Type t = typeof (T); } }
The default Generic Value
The default keyword can be used to get the default value given a generic type parameter. The default value for a reference type is null and for value type is the result of bitwise-zeroing.
static void Zap<T> (T[] array) { for (int i = 0; i < array.Length; i++) array[i] = default(T); } static void Main() { int[] numbers = { 1, 2, 3 }; Zap (numbers); numbers.Dump(); }
Generic Constraints
By default the type parameter can be substituted with any type. Constraints can be applied to a type parameter restrict the type arguments. It can be applied wherever type parameters are defined, both in methods or type definitions.
Possible Constraints:
where T : base-class // Base class constraint where T : interface // Interface constraint where T : class // Reference-type constraint where T : struct // Value-type constraint (excludes Nullable types) where T : new() // Parameterless constructor constraint where U : T // Naked type constraint
In this example the class GenericClass derives T from SomeClass and implement Interface1 and requires U to provide a parameterless constructor.
class SomeClass {} interface Interface1 {} class GenericClass<T,U> where T : SomeClass, Interface1 where U : new() {}
Base-class constraint
Specifies that the type parameter must subclass or match a particular class.
Interface constraint
Specifies that the type parameter must implement that interface.
Example:
To write generic Max method, which returns the maximum of two values. The IComparable<T> generic interface can be used to write the Max method.
//Simplified version of interface public interface IComparable{ int CompareTo (T other) }
The CompareTo method returns a positive number which is greater between the two numbers.
Interface constraint can be used to write the Max method to avoid distraction and null checking is omitted.
using System; namespace Rextester { public class Program { public static void Main(string[] args) { //With this method the Max method can accept any type of arguments. int z = Max (5, 10); // 10 string last = Max ("ant", "zoo"); // zoo Console.WriteLine(z); Console.WriteLine(last); } // Self-referencing interface constraint static T Max <T> (T a, T b) where T : IComparable<T> { return a.CompareTo (b) > 0 ? a : b; } } class SomeClass{} interface Interface1{} // Class & interface constraint class GenericClass<T> where T : SomeClass, Interface1 {} }
Class constraint and Struct constraint
Specify that T must be a reference type or value type (non-nullable).
Example: Struct constraint
System.Nullable<T> struct
struct Nullable<T> where T : struct {...}
Parameterless Constructor constraint
Requires T to have public parameterless constructor, you can call new() on T.
static void Initialize<T> (T[] array) where T : new() { for (int i = 0; i < array.Length; i++) array[i] = new T(); } static void Main() { var builders = new StringBuilder[100]; Initialize (builders); builders[37].Dump(); }
Naked type constraint
Requires one type parameter to derive from another type parameter.
In this example, the method FilteredStack returns another Stack, containing only the subset of elements where the type parameter U is of the type parameter T.
{ Stack<U> FilteredStack<U>() where U : T { /* ... */ return default(Stack<U>); } } static void Main() { }
Subclassing Generic Types
// A generic class can be subclassed just like a nongeneric class. // The subclass can leave the base class’s type parameters open: class Stack{ /*...*/ } class SpecialStack<T> : Stack<T> { /*...*/ } // Or the subclass can close the generic type parameters with a concrete type: class IntStack : Stack<int> { /*...*/ } // A subtype can also introduce fresh type arguments: class List<T> { /*...*/ } class KeyedList<T,TKey> : List<T> { /*...*/ } static void Main() { }
Self-Referencing Generic Declarations
// A type can name itself as the concrete type when closing a type argument: public class Balloon : IEquatable{ public string Color { get; set; } public int CC { get; set; } public bool Equals (Balloon b) { if (b == null) return false; return b.Color == Color && b.CC == CC; } // In real life, we would override object.Equals / GetHashCode as well - see Chapter 6. } static void Main() { var b1 = new Balloon { Color = "Red", CC = 123 }; var b2 = new Balloon { Color = "Red", CC = 123 }; b1.Equals (b2).Dump(); }
Static Data
// Static data is unique for each closed type: class Bob<T> { public static int Count; } static void Main() { Console.WriteLine (++Bob<int>.Count); // 1 Console.WriteLine (++Bob<int>.Count); // 2 Console.WriteLine (++Bob<string>.Count); // 1 Console.WriteLine (++Bob<object>.Count); // 1 }
Type Parameters and Conversions
C# cast operator can perform several conversions:
- Numeric conversion
- Reference conversion
- Boxing/unboxing conversion
- Custom conversion
// The most common scenario is when you want to perform a reference conversion: StringBuilder Foo<T> (T arg) { if (arg is StringBuilder) // Will not compile: Cannot convert T to StringBuilder return (StringBuilder) arg; /*...*/ return null; } static void Main() { }
The conversion will be decided by the compiler based on the operands, if there is ambiguity than it generates error. To avoid the ambiguity issue you can use the as operator.
StringBuilder Foo<T> (T arg) { StringBuilder sb = arg as StringBuilder; if (sb != null) return sb; /*...*/ return null; } static void Main() { }
Another approach will be to use cast to object
StringBuilder Foo<T> (T arg) { if (arg is StringBuilder) return (StringBuilder) (object) arg; /*...*/ return null; } static void Main() { }
Covariance
If A is convertible to B and X has a covariant type parameter X<A> which is convertible to X<B>. Interfaces permit covariant type parameters but classes do not.
// Generic classes are not covariant, to ensure static type safety. Consider the following: class Animal {} class Bear : Animal {} class Camel : Animal {} public class Stack<T> // A simple Stack implementation { int position; T[] data = new T[100]; public void Push (T obj) => data[position++] = obj; public T Pop() => data[--position]; } static void Main() { // The following fails to compile: Stack<Bear> bears = new Stack<Bear>(); Stack<Animal> animals = bears; // Compile-time error // That restriction prevents the possibility of runtime failure with the following code: animals.Push (new Camel()); // Trying to add Camel to bears }
Variance is not automatic
To ensure static type safety, type parameters are not automatically variant.
// Lack of covariance with classes can hinder reusability. class Animal {} class Bear : Animal {} class Camel : Animal {} public class Stack<T> // A simple Stack implementation { int position; T[] data = new T[100]; public void Push (T obj) => data[position++] = obj; public T Pop() => data [--position]; } static class ZooCleaner { public static void Wash (Stack<Animal> animals) { /*...*/ } } static void Main() { Stack<Bear> bears = new Stack<Bear>(); ZooCleaner.Wash (bears); // Will not compile! }
Workaround:
class Animal {} class Bear : Animal {} class Camel : Animal {} public class Stack<T> // A simple Stack implementation { int position; T[] data = new T[100]; public void Push (T obj) => data[position++] = obj; public T Pop() => data[--position]; } static class ZooCleaner { public static void Wash<T>(Stack<T> animals) where T : Animal { /*...*/ } } static void Main() { Stack<Bear> bears = new Stack<Bear>(); ZooCleaner.Wash (bears); // Works! }
Arrays
Array types support covariance. B[] can be cast to A[] if B subclasses A.
class Animal {} class Bear : Animal {} class Camel : Animal {} static void Main() { // For historical reasons, array types are covariant. Bear[] bears = new Bear[3]; Animal[] animals = bears; // OK // The downside of this reusability is that element assignments can fail at runtime: animals[0] = new Camel(); // Runtime error }
Interfaces
Interfaces and delegates can be declared covariant by marking them with the out modifier. The out modifier ensures that covariant type parameters are fully type-safe.
The out modifier on T indicates that T is used only for output positions (return types) and it flags the type parameter as covariant
public interface IPoppable<out T> { T Pop(); } class Animal {} class Bear : Animal {} class Camel : Animal {} public class Stack<T> : IPoppable<T> { int position; T[] data = new T [100]; public void Push (T obj) => data [position++] = obj; public T Pop() => data [--position]; } static void Main() { var bears = new Stack<Bear>(); bears.Push (new Bear()); // Bears implements IPoppable. We can convert to IPoppable : IPoppable<Animal> animals = bears; // Legal Animal a = animals.Pop(); } // This is also now legal: class ZooCleaner { public static void Wash (IPoppable<Animal> animals) { /*...*/ } }
Contravariance
Is the reverse direction of covariant from X<B> to X<A>.Only supported if the type parameter is in the input position and designated with the in modifier.
// Type parameters marked with the in modifier indicate contravariance: public interface IPoppable<out T> { T Pop(); } public interface IPushable<in T> { void Push (T obj); } class Animal {} class Bear : Animal {} class Camel : Animal {} // Note that Stack<T> can implement both IPoppable<T> and IPushable<T>: public class Stack<T> : IPoppable<T>, IPushable<T> { int position; T[] data = new T[100]; public void Push (T obj) => data[position++] = obj; public T Pop() => data[--position]; } static void Main() { IPushable<Animal> animals = new Stack<Animal>(); IPushable<Bear> bears = animals; // Legal bears.Push (new Bear()); }
/* The following interface is defined as part of the .NET Framework: public interface IComparer<in T> { // Returns a value indicating the relative ordering of a and b int Compare (T a, T b); } */ static void Main() { var objectComparer = Comparer<object>.Default; IComparer<string> stringComparer = objectComparer; int result = stringComparer.Compare ("Brett", "Jemaine"); result.Dump(); }