Essential C# 5.0 Note - Chapter 11 - Generics

Nullable modifier ? when decaring a value type variable

Drawbacks of Non-Generics

Lack of type checking

e.g. System.Collections.Stack

  • Lack of validation check for which type pushed into stack.

  • No safe check for explicitly casting for the pop item into type.

Boxing and Unboxing with heave performance impact

Boxing

Value Type ========> Reference Type +++++ more overhead performance impact (allocate memory -> copy the value -> garbage-collect that memory)

Unboxing

Reference Type ==========> Value Type + negligible performance impact

Benefits of Generics

There are several advantages to using a generic class over a nongeneric version

(such as the System.Collections.Generic.Stack class used earlier instead of the original System.Collections.Stack type.

No longer need to cast the return of path.Pop() or ensure that only Cell type objects are added to path in the Push() method.)

  1. Generics allow you to author algorithms and patterns, and reuse the code for differernt datat types.

  2. Generics facilitate increased type safety , preventing data types other than those explicitly intended by the members within the parameterized class.
    In Listing 11.7, the parameterized stack class restricts you to the Cell data type when using Stack. (For example, the statement path.Push(“garbage”) produces a compile-time error indicating that there is no overloaded method for System.Collections.Generic.Stack.Push(T) that can work with the string, because it cannot be converted to a Cell.)

public void Sketch()
{
Stack<Cell> path; // Generic variable declaration
path = new Stack<Cell>(); // Generic object instantiation

...
}

public class Stack<T>
{
private T[] _Items;

public void Push(T data)
{
...
}

public T Pop()
{
...
}
}
  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 boxing conversion to object. (For example, path.Pop() and path.Push() do not require an item to be boxed when added or unboxed when removed.)

  3. Performance improves because casting from an object is no longer required, thus eliminating a type check operation. Also, performance improves because boxing is no longer necessary for value types.

  4. Generics reduce memory consumption by avoiding boxing and thus consuming less memory on the heap.

  5. 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.)

  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.

Type Parameter Naming Guidelines

DO choose meaningful names for type parameters and prefix the name with “T”.

CONSIDER indicating a constraint in the name of a type parameter.

Generic Interfaces and Structs

interface IPair<T>
{
T First { get; set; }
T Second { get; set; }
}

public struct Pair<T>: IPair<T>
{
public T First
{
get
{
return _First;
}
set
{
_First = value;
}
}
private T _First;

public T Second
{
get
{
return _Second;
}
set
{
_Second = value;
}
}
private T _Second;
}

AVOID implementing multiple constructions of the same generic interface in one type.

Defining a Constructor and a Finalizer

Perhaps surprisingly, the constructors (and finalizer) of a generic class or struct do not require type parameters.

public struct Pair<T>: IPair<T>
{
public Pair(T first, T second)
{

_First = first;
_Second = second;
}


public T First
{
get{ return _First; }
set{ _First = value; }
}
private T _First;

public T Second
{
get{ return _Second; }
set{ _Second = value; }
}
private T _Second;

Specifying a Default Value

public struct Pair<T>: IPair<T>
{
public Pair(T first)
{

_First = first;
_Second = default(T);

}

// ...
}

Multiple Type Parameters

interface IPair<TFirst, TSecond>
{
TFirst First { get; set; }
TSecond Second { get; set; }
}

public struct Pair<TFirst, TSecond>: IPair<TFirst, TSecond>
{
public Pair(TFirst first, TSecond second)
{
_First = first;
_Second = second;
}

public TFirst First
{
get{ return _First; }
set{ _First = value; }
}
private TFirst _First;

public TSecond Second
{
get{ return _Second; }
set{ _Second = value; }
}
private TSecond _Second;
}

Pair<int, string> historicalEvent = new Pair<int, string>(1914, "Shackleton leaves for South Pole on ship Endurance");
Console.WriteLine("{0}: {1}", historicalEvent.First, historicalEvent.Second);

The number of type parameters, the arity, uniquely distinguishes the class from others of the same name.

Therefore, it is possible to define both Pair and Pair within the same namespace because of the arity variation.

DO place multiple generic classes into a single file if they only differ by the number of generic parameters.

Nested Generic Types

Type parameters on a containing generic type will “cascade” down to any nested types automatically.

If the containing type declares a type parameter T, for example, all nested types will also be generic and type parameter T will be available on the nested type as well.

If the nested type includes its own type parameter named T, this will hide the type parameter within the containing type and any reference to T in the nested type will refer to the nested T type parameter.

class Container<T, U>
{
// Nested classes inherit type parameters.
// Reusing a type parameter name will cause
// a warning.
class Nested<U>
{
void Method(T param0, U param1)

{
}
}
}

The rule is simply that a type parameter is available anywhere within the body of the type that declares it.

AVOID shadowing a type parameter of an outer type with an identically named type parameter of a nested type.

Generics with constraints

A constraint declares the characteristics that the generic type requires of the type argument supplied for each type parameter.

You declare a constraint using the where keyword, followed by a parameter-requirements pair, where the parameter must be one of those declared in the generic type and the requirements describe the class or interfaces to which the type argument must be convertible, the presence of a default constructor, or a reference/value type restriction.

where T : iInterface, new()

Interface Constraints

public class BinaryTree<T>
where T: System.IComparable<T>
public class BinaryTree<T>
where T: System.IComparable<T>

{
...
public Pair<BinaryTree<T>> SubItems
{
get{ return _SubItems; }
set
{
IComparable<T> first;
// Notice that the cast can now be eliminated.
first = value.First.Item;

if (first.CompareTo(value.Second.Item) < 0)

{
// first is less than second
...
}
else
{
// second is less than or equal to first.
...
}
_SubItems = value;
}
}
private Pair<BinaryTree<T>> _SubItems;
}

Class Type Constraints

public class EntityDictionary<TKey, TValue>
: System.Collections.Generic.Dictionary<TKey, TValue>
where TValue : EntityBase
{
...
}

Class type constraints must appear before any interface type constraints (just as the base class must appear before implemented interfaces in a class declaration).

Multiple base class constraints are not allowed since it is not possible to derive from multiple unrelated classes.

Similarly, base class constraints cannot specify sealed classes or nonclass types.

Certain “special” types are not legal as class type constraints.

Struct/Class Constraints

class constraint restricts it to reference types. e.g. any class, interface, delegate, or array type. struct constraint - Nullable value types do not satisfy the constraint.

Why?

Nullable value types are implemented as the generic type Nullable, which itself applies the struct constraint to T. If nullable value types satisfied that constraint, it would be possible to define the nonsense type Nullable>. A doubly nullable integer is confusing to the point of being meaningless. (As expected, the shorthand syntax int?? is also disallowed.)

Multiple Constraints

public class EntityDictionary<TKey, TValue>
: Dictionary<TKey, TValue>
where TKey : IComparable<TKey>, IFormattable
where TValue : EntityBase
{
...
}

When specifying multiple constraints on one type parameter, an AND relationship is assumed.

If a type C is supplied as the type argument for TKey, C must implement IComparable and IFormattable, for example.

Constructor Constraints

public class EntityBase<TKey>
{
public TKey Key
{
get{ return _Key; }
set{ _Key = value; }
}
private TKey _Key;
}

public class EntityDictionary<TKey, TValue> :
Dictionary<TKey, TValue>
where TKey: IComparable<TKey>, IFormattable
where TValue : EntityBase<TKey>, new()

{
// ...

public TValue MakeValue(TKey key)
{
TValue newEntity = new TValue();

newEntity.Key = key;
Add(newEntity.Key, newEntity);
return newEntity;
}

// ...
}

Constructor constraint, and it requires the type argument corresponding to the constrained type parameter to have a public default constructor.

Only the default constructor constraint is available.

You cannot specify a constraint that ensures that the type argument supplied provides a constructor that takes formal parameters.

Constraint Inheritance

Neither generic type parameters nor their constraints are inherited by a derived class, because generic type parameters are not members.

(Remember, class inheritance is the property that the derived class has all of the members of the base class.)

It is a common practice to make new generic types that inherit from other generic types.

Since the type parameters of the derived generic type are now the type arguments of the generic base class, the type parameters must have equal (or stronger) constraints as those on the base class.

Confused? Consider Listing 11.27.

Listing 11.27. Inherited Constraints Specified Explicitly

class EntityBase<T> where T : IComparable<T>
{
// ...
}

class EntityBase
{
// ...
public virtual void Method<U>(U u) where U : IComparable<U>
{
// ...
}
}

class Order : EntityBase
{
public override void Method<T>(T t) // where T : IComparable<T>
// Constraints may not be repeated on overriding
// members

{
// ...
}
}
// ERROR:
// The type 'U' must be convertible to
// 'System.IComparable<U>' in order to use it as parameter
// 'T' in the generic type or method.
class Entity<U> : EntityBase<U> where U : IComparable<U>
{
//...
}

But briefly, methods may also be generic and may also place constraints on the type arguments supplied for their type parameters.

How, then, are constraints handled when a virtual generic method is inherited and overridden?

In contrast to the situation with type parameters declared on a generic class, constraints on overriding virtual generic methods (or explicit interface) methods are inherited implicitly and may not be restated.

Repeating Inherited Constraints on Virtual Members Is Prohibited

In the generic class inheritance case, the type parameter on the derived class can be additionally constrained by adding not only the constraints on the base class (required), but also additional constraints as well.

However, overriding virtual generic methods need to conform exactly to the constraints defined by the base class method.

Additional constraints could break polymorphism, so they are not allowed and the type parameter constraints on the overriding method are implied.In

Advanced Topic: Constraint Limitations

Constraints are appropriately limited to avoid nonsensical code.

  1. Cannot combine a class type constraint with a struct or class constraint.

  2. Cannot specify constraints to restrict inheritance to special types such as object, arrays, System.ValueType, System.Enum (enum), System.Delegate, and System.MulticastDelegate.

  3. Operator Constraints Are Not Allowed

You cannot constrain a type parameter to a type that implements a particular method or operator, except via interface type constraints (for methods) or class type constraints (for methods and operators).

public abstract class MathEx<T>
{
public static T Add(T first, T second)
{
// Error: Operator '+' cannot be applied to
// operands of type 'T' and 'T'.
return first + second;
}
}

More generally, there is no way to constrain a type to have a static method.

  1. OR Criteria Are Not Supported

If you supply multiple interfaces or class constraints for a type parameter, the compiler always assumes an AND relationship between constraints.If

There is no way to specify an OR relationship between constraints.

Supporting this would prevent the compiler from resolving which method to call at compile time.

  1. Constraints of Type Delegate and Enum Are Not Valid

Delegate types, array types, and enumerated types may not be used as class type constraints, because they are all effectively “sealed” types.

Their base types, System.Delegate, System.MultiCastDelegate, System.Array, and System.Enum, may also not be used as constraints.

public class Publisher<T>
where T : System.Delegate
{
public event T Event;
public void Publish()
{
if (Event != null)
{
Event(this, new EventArgs());
}
}
}

All delegate types are considered special classes that cannot be specified as type parameters.

Doing so would prevent compile-time validation on the call to Event() because the signature of the event firing is unknown with the data types System.Delegate and System.MulticastDelegate.

The same restriction occurs for any enum type.

6.Constructor Constraints Are Allowed Only for Default Constructors

There is no constraint to force the type argument to provide a constructor that takes other formal parameters.

One way to circumvent this restriction is to supply a factory interface that includes a method for instantiating the type.

The factory implementing the interface takes responsibility for instantiating the entity rather than the EntityDictionary itself

public class EntityBase<TKey>
{
public EntityBase(TKey key)
{
Key = key;
}

public TKey Key
{
get { return _key; }
set { _key = value; }
}
private TKey _key;
}

public class EntityDictionary<TKey, TValue, TFactory> :
Dictionary<TKey, TValue>
where TKey : IComparable<TKey>, IFormattable
where TValue : EntityBase<TKey>
where TFactory : IEntityFactory<TKey, TValue>, new()
{
...
public TValue New(TKey key)
{
TFactory factory = new TFactory();
TValue newEntity = factory.CreateNew(key);

Add(newEntity.Key, newEntity);
return newEntity;
}
...
}

public interface IEntityFactory<TKey, TValue>
{
TValue CreateNew(TKey key);
}

public class Order : EntityBase<Guid>
{
public Order(Guid key) :
base(key)
{
// ...
}
}

public class OrderFactory : IEntityFactory<Guid, Order>
{
public Order CreateNew(Guid key)
{
return new Order(key);
}
}

Generic Methods

public static class MathEx
{
public static T Max<T>(T first, params T[] values)
where T : IComparable<T>
{
T maximum = first;
foreach (T item in values)
{
if (item.CompareTo(maximum) > 0)
{
maximum = item;
}
}
return maximum;
}



public static T Min<T>(T first, params T[] values)
where T : IComparable<T>
{
T minimum = first;

foreach (T item in values)
{
if (item.CompareTo(minimum) < 0)
{
minimum = item;
}
}
return minimum;
}
}

Generic Method Type Inference

In cases where method type inference is still not sophisticated enough to deduce the type arguments, you can resolve the error by either inserting casts on the arguments that clarify to the compiler the argument types that should be used in the inferences, or giving up on type inferencing and including the type arguments explicitly.

Also note that when making its inferences, the method type inference algorithm considers only the arguments, the arguments’ types, and the formal parameter types of the generic method.

Other factors that could in practice be used in the analysis—such as the return type of the generic method, the type of the variable that the method’s returned value is being assigned to, or the constraints on the method’s generic type parameters—are not considered at all by the method type inference algorithm.

public static class MathEx
{
public static T Max<T>(T first, params T[] values)
where T : IComparable<T>
{
T maximum = first;
foreach (T item in values)
{
if (item.CompareTo(maximum) > 0)
{
maximum = item;
}
}
return maximum;
}

public static T Max<T, V>(T first, V second, V[] seconds, params T[] values)
where T : IComparable<T>
where V : IComparable<V>
{
T maximum = first;
foreach (T item in values)
{
if (item.CompareTo(maximum) > 0)
{
maximum = item;
}
}
return maximum;
}

public static T Min<T>(T first, params T[] values)
where T : IComparable<T>
{
T minimum = first;

foreach (T item in values)
{
if (item.CompareTo(minimum) < 0)
{
minimum = item;
}
}
return minimum;
}

static void Main()
{

Console.WriteLine(MathEx.Max<int>(7, 490));

Console.WriteLine(MathEx.Min<string>("R.O.U.S.", "Fireswamp"));

Console.WriteLine(MathEx.Max(7, 490)); // No type arguments!

Console.WriteLine(MathEx.Min("R.O.U.S'", "Fireswamp"));
}
}

Specifying Constraints

Type parameters of generic methods may be constrained in exactly the same way that type parameters of generic types are constrained.Constraints

public static void Show<T>(BinaryTree<T> tree, int indent) where T :  IComparable<T>
{
...
}

Notice that the Show implementation itself does not directly use any member of the IComparable interface, so you might wonder why the constraint is required. Recall, however, that the BinaryTree class did require this

public class BinaryTree<T>
where T: System.IComparable<T>

{
...
}

Because the BinaryTree class requires this constraint on its T, and because Show uses its T as a type argument corresponding to a constrained type parameter, Show needs to ensure that the class’s type parameter’s constraint is met on its method type argument.

Advanced Topic: Casting insdie a Generic Method

Sometimes you should be wary of using generics

Advanced Topic: Casting inside a Generic Method

Sometimes you should be wary of using generics—for instance, when using them specifically to bury a cast operation. Consider the following method, which converts a stream into an object of a given type:

public static T Deserialize<T>(
Stream stream, IFormatter formatter)
{
return (T)formatter.Deserialize(stream);
}

The formatter is responsible for removing data from the stream and converting it to an object. The Deserialize() call on the formatter returns data of type object. A call to use the generic version of Deserialize() looks something like this:

string greeting =
Deserialization.Deserialize<string>(stream, formatter);

The problem with this code is that to the caller of the method, Deserialize() appears to be type-safe. However, a cast operation is still performed on behalf of the caller, as in the case of the nongeneric equivalent shown here:

string greeting =
(string)Deserialization.Deserialize(stream, formatter);

The cast could fail at runtime; the method might not be as type-safe as it appears. The Deserialize method is generic solely so that it can hide the existence of the cast from the caller, which seems dangerously deceptive. It might be better for the method to be nongeneric and return object, making the caller aware that it is not type-safe. Developers should use care when casting in generic methods if there are no constraints to verify cast validity.

Guidelines

AVOID misleading the caller with generic methods that are not as type-safe as they appear.

Covariance and Contravariance

A common question asked by new users of generic types is why an expression of type List may not be assigned to a variable of type List—if a string may be converted to type object, surely a list of strings is similarly compatible with a list of objects. But this is not, generally speaking, either type-safe or legal.

If you declare two variables with different type parameters using the same generic class, the variables are not type-compatible even if they are assigning from a more specific type to a more generic type—in other words, they are not covariant.

Covariant is a technical term from category theory, but the idea is straightforward: Suppose two types X and Y have a special relationship, namely that every value of the type X may be converted to the type Y. If the types I and I always also have that same special relationship, we say, “I is covariant in T.” When dealing with simple generic types with only one type parameter, the type parameter can be understood and we simply say, “I is covariant.” The conversion from I to I is called a covariant conversion.

For example, instances of a generic class, Pair and Pair, are not type-compatible even when the type arguments are themselves compatible. In other words, the compiler prevents converting (implicitly or explicitly) Pair to Pair, even though Contact derives from PdaItem. Similarly, converting Pair to the interface type IPair will also fail.For

// ...
// Error: Cannot convert type ...
Pair<PdaItem> pair = (Pair<PdaItem>) new Pair<Contact>();
IPair<PdaItem> duple = (IPair<PdaItem>) new Pair<Contact>();
//...
Contact contact1 = new Contact("Princess Buttercup"),
Contact contact2 = new Contact("Inigo Montoya");
Pair<Contact> contacts = new Pair<Contact>(contact1, contact2);

// This gives an error: Cannot convert type ...
// But suppose it did not.
// IPair<PdaItem> pdaPair = (IPair<PdaItem>) contacts;
// This is perfectly legal, but not type safe.
// pdaPair.First = new Address("123 Sesame Street");
...

An IPair can contain an address, but the object is really a Pair that can only contain contacts, not addresses.

Type safety is completely violated if unrestricted generic covariance is allowed

Now it should also be clear why a list of strings may not be used as a list of objects; you cannot insert an integer into a list of strings, but you can insert an integer into a list of objects, so it must be illegal to cast a list of strings to a list of objects so that this error can be prevented by the compiler.Contact

out

Support for safe covariance was added to C# 4. To indicate that a generic interface is intended to be covariant in one of its type parameters, declare the type parameter with the out type parameter modifier.

interface IReadOnlyPair<out T>
{
T First { get; }
T Second { get; }
}

There are a number of important restrictions on covariant conversions.

*Only generic interfaces and generic delegates (described in Chapter 12) may be covariant. Generic classes and structs are never covariant.

*The varying type arguments of both the “source” and “target” generic types must be reference types, not value types. That is, an IReadOnlyPair may be converted covariantly to IReadOnlyPair because both string and IReadOnlyPair are reference types. An IReadOnlyPair may not be converted to IReadOnlyPair because int is not a reference type.

*The interface or delegate must be declared as supporting covariance, and the compiler must be able to verify that the annotated type parameters are in fact used in only “output” positions.IReadOnlyPair

Generic Internals

Given the discussions in earlier chapters about the prevalence of objects within the CLI type system, it is no surprise that generics are also objects.

In fact, the type parameter on a generic class becomes metadata that the runtime uses to build appropriate classes when needed. Generics, therefore, support inheritance, polymorphism, and encapsulation.

With generics, you can define methods, properties, fields, classes, interfaces, and delegates.

To achieve this, generics require support from the underlying runtime.

So, the addition of generics to the C# language is a feature of both the compiler and the platform.

To avoid boxing, for example, the implementation of generics is different for value-based type parameters than for generics with reference type parameters.

.class private auto ansi beforefieldinit
    Stack'1<([mscorlib]System.IComparable)T>
    extends [mscorlib]System.Object
{
    field private !0[ ] items
}

The first notable item is the ‘1 that appears following Stack on the second line. That number is the arity. It declares the number of type parameters that the generic class will require type arguments for. A declaration such as EntityDictionary would have an arity of 2.

The second line of the generated CIL shows the constraints imposed upon the class. The T type parameter is decorated with an interface declaration for the IComparable constraint.

If you continue looking through the CIL, you will find that the item’s array declaration of type T is altered to contain a type parameter using “exclamation point notation,” used in the generics-capable version of the CIL.

The exclamation point denotes the presence of the first type parameter specified for the class.

Beyond the inclusion of the arity and type parameter in the class header and the type parameter denoted with exclamation points in code, there is little difference between the CIL generated for a generic class and the CIL generated for a nongeneric class.

Instantiating Generics Based on Value Types

When a generic type is first constructed with a value type as a type parameter, the runtime creates a specialized generic type with the supplied type parameter(s) placed appropriately in the CIL.

Therefore, the runtime creates new specialized generic types for each new parameter value type.

You declare two instances of a Stack, both using the code already generated by the runtime for a Stack.

Stack<int> stackOne = new Stack<int>();
Stack<int> stackTwo = new Stack<int>();

If later in the code, you create another Stack with a different value type substituted for the type parameter (such as a long or a user-defined struct) the runtime generates another version of the generic type.

The benefit of specialized value type classes is better performance. Furthermore, the code is able to avoid conversions and boxing because each specialized generic class “natively” contains the value type.

Instantiating Generics Based on Reference Types

The first time a generic type is constructed with a reference type, the runtime creates a specialized generic type with object references substituted for type parameters in the CIL, not a specialized generic type based on the type argument.

Each subsequent time a constructed type is instantiated with a reference type parameter, the runtime reuses the previously generated version of the generic type, even if the reference type is different from the first reference type.

This implementation of generics greatly reduces code bloat by reducing to one the number of specialized classes created by the compiler for generic classes of reference types.

Even though the runtime uses the same internal generic type definition when the type parameter on a generic reference type varies, this behavior is superseded if the type parameter is a value type. Dictionary, Dictionary, and Dictionary will require new internal type definitions, for example.

Static Class with Generics

“This” and extension method

nullable value