C# – Generics

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();
}

C# – Nested Types

Nested Types

  • Nested type is declared within the scope of another type.
  • It can access the enclosing type’s private members and everything else the enclosing type can access.
  • It can be declared with the full range of access modifiers, rather than just public and internal.
  • The default accessibility for a nested type is private rather than internal.
  • Accessing a nested type from outside the enclosing type requires qualification with the enclosing type’s name (like when accessing static members).
  • All types (classes, structs, interfaces, delegates and enums) can be nested inside
    either a class or a struct.
public class TopLevel
{
	public class Nested { }               // Nested class
	public enum Color { Red, Blue, Tan }  // Nested enum
}

static void Main()
{
	TopLevel.Color color = TopLevel.Color.Red;	
}

//Private member visibility
public class TopLevel
{
	static int x;
	public class Nested
	{
		public static void Foo() { Console.WriteLine (TopLevel.x); }
	}
}

static void Main()
{
	TopLevel.Nested.Foo();
}

//Protected member visibility
public class TopLevel
{
	protected class Nested { }
}

public class SubTopLevel : TopLevel
{
	static void Foo() { new TopLevel.Nested(); }
}

static void Main()
{
}


C# – Enums

Enums

  • System.Enum
  • Special value types that specifies a group of named numeric constants.
  • It has a underlying values of type int
  • The constants are automatically assigned and can be specified explicitly also.
public enum BorderSide { Left, Right, Top, Bottom }

static void Main()
{
	BorderSide topSide = BorderSide.Top;
	bool isTop = (topSide == BorderSide.Top);	
	isTop.Dump();
}

// You may specify an alternative integral type:
public enum BorderSideByte : byte { Left, Right, Top, Bottom }

// You may also specify an explicit underlying value for each enum member:
public enum BorderSideExplicit : byte { Left=1, Right=2, Top=10, Bottom=11 }

public enum BorderSidePartiallyExplicit : byte { Left=1, Right, Top=10, Bottom }

Enum Conversions

Three ways to represent enum value are: enum member, integral value, string.

You can convert an enum instance to and from its underlying integral value with an
explicit cast. You can also explicitly cast one enum type to another. There are two reasons for the special treatment of 0 : first member of an enum is often used as the ‘default’ value and secondly for combined enum types, 0 means ‘no flags’.

public enum BorderSide { Left, Right, Top, Bottom }

public enum HorizontalAlignment
{
	Left = BorderSide.Left,
	Right = BorderSide.Right,
	Center
}

static void Main()
{
	// You can convert an enum instance to and from its underlying integral value with an explicit cast:
	
	int i = (int) BorderSide.Left;
	i.Dump ("i");
	
	BorderSide side = (BorderSide) i;
	side.Dump ("side");
	
	bool leftOrRight = (int) side <= 2;
	leftOrRight.Dump ("leftOrRight");
	
	HorizontalAlignment h = (HorizontalAlignment) BorderSide.Right;
	h.Dump ("h");
	
	BorderSide b = 0;    // No cast required with the 0 literal.
	b.Dump ("b");
}

Flags Enums

You can combine enum members. To prevent ambiguities, members of a combinable enum require explicitly assigned values, typically in powers of two.

[Flags]
public enum BorderSides { None=0, Left=1, Right=2, Top=4, Bottom=8 }

static void Main()
{
	BorderSides leftRight = BorderSides.Left | BorderSides.Right;
	
	if ((leftRight & BorderSides.Left) != 0)
		Console.WriteLine ("Includes Left");   // Includes Left
	
	string formatted = leftRight.ToString();   // "Left, Right"
	
	BorderSides s = BorderSides.Left;
	s |= BorderSides.Right;
	Console.WriteLine (s == leftRight);   // True
	
	s ^= BorderSides.Right;               // Toggles BorderSides.Right
	Console.WriteLine (s);                // Left
}

Enum Operators

Addition is possible between enum and integral type but not between 2 enums.

= == != > < >= <= + -
^ & | ~ += -= ++ -- sizeof
[Flags]
public enum BorderSides
{
	None=0,
	Left=1, Right=2, Top=4, Bottom=8,
	LeftRight = Left | Right, 
	TopBottom = Top  | Bottom,
	All       = LeftRight | TopBottom
}

static void Main()
{
	BorderSides.All.Dump();
	
	// The bitwise, arithmetic, and comparison operators return the result of processing
	// the underlying integral values:	
	(BorderSides.All ^ BorderSides.LeftRight).Dump();
}

Type-Safety Issues

Since an enum can be cast to and from its underlying integral type, the actual value may fall outside the bounds of a legal enum member.

public enum BorderSide { Left, Right, Top, Bottom }

static void Main()
{
	// Since an enum can be cast to and from its underlying integral type, the actual value
	// it may have may fall outside the bounds of a legal enum member:
	BorderSide b = (BorderSide) 12345;
	Console.WriteLine (b);                // 12345
	
	BorderSide b2 = BorderSide.Bottom;
	b2++;									// No errors
	Console.WriteLine (b2);					// 4 (illegal value)
}

// An invalid BorderSide would break the following method:

void Draw (BorderSide side)
{
	if      (side == BorderSide.Left)  { /*...*/ }
	else if (side == BorderSide.Right) { /*...*/ }
	else if (side == BorderSide.Top)   { /*...*/ }
	else                               { /*...*/ }  // Assume BorderSide.Bottom
}

//Workaround
[Flags]
public enum BorderSides { Left=1, Right=2, Top=4, Bottom=8 }

static bool IsFlagDefined (Enum e)
{
	decimal d;
	return !decimal.TryParse (e.ToString(), out d);
}

static void Main()
{
	for (int i = 0; i <= 16; i++)
	{
		BorderSides side = (BorderSides)i;
		Console.WriteLine (IsFlagDefined (side) + " " + side);
	}
}

Type unification means you can implicitly cast any enum member to a System.Enum.

enum Nut  { Walnut, Hazelnut, Macadamia }
enum Size { Small, Medium, Large }

static void Main()
{
	Display (Nut.Macadamia);     // Nut.Macadamia
	Display (Size.Large);        // Size.Large
}

static void Display (Enum value)		// The Enum type unifies all enums
{
	Console.WriteLine (value.GetType().Name + "." + value.ToString());
}