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
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.)
Generics allow you to author algorithms and patterns, and reuse the code for differernt datat types.
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() |
Compile-time type checking reduces the likelihood of
InvalidCastException
type errors at runtime.Using value types with generic class members no longer causes a boxing conversion to
object
. (For example,path.Pop()
andpath.Push()
do not require an item to be boxed when added or unboxed when removed.)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.
Generics reduce memory consumption by avoiding boxing and thus consuming less memory on the heap.
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.)
Code becomes more readable because of fewer casting checks and because of the need for fewer type-specific implementations.
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> |
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> |
Specifying a Default Value
public struct Pair<T>: IPair<T> |
Multiple Type Parameters
interface IPair<TFirst, TSecond> |
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
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> |
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> |
public class BinaryTree<T> |
Class Type Constraints
public class EntityDictionary<TKey, TValue> |
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
Multiple Constraints
public class EntityDictionary<TKey, TValue> |
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
Constructor Constraints
public class EntityBase<TKey> |
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> |
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.
Cannot combine a class type constraint with a struct or class constraint.
Cannot specify constraints to restrict inheritance to special types such as object, arrays, System.ValueType, System.Enum (enum), System.Delegate, and System.MulticastDelegate.
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> |
More generally, there is no way to constrain a type to have a static method.
- 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.
- 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> |
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> |
Generic Methods
public static class MathEx |
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 |
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
public class BinaryTree<T> |
Because the BinaryTree
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>( |
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, Deserializestring 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
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
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
For example, instances of a generic class, Pair
// ... |
//... |
An IPair
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> |
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
*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
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
Stack<int> stackOne = 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