Casting and generics

Basic rules

From now on, we will suppose that T is a parameter type. The C#2 language allows you to:

Cast implicitly an instance of a type T (if T is a value type, else a reference of type Tobjet type. If T is a value type, a boxing operation will occur.

Cast explicitly a reference of type objet to an instance of type T. If T is a value type, there will be an unboxing operation.

Cast explicitly an instance of a type T to a reference of any interface. If T is a value type, a boxing operation will occur.

Cast explicitly a reference of any interface to an instance of the T type. If T is a value type, there will be an unboxing operation.

In the last three cases, if the cast is not possible, an exception of type InvalidCastException is raised.

Other casting rules are added if we use derivation constraints:

If T is constrained to implement the interface I, you can implicitly cast an instance from T into I or into any interface implemented by I and vice versa. If T is a value type, a boxing operation (or unboxing) will occur.

If T is constrained to derive from the C class, you can implicitly cast an instance of T to C or into any sub-class of C and vice versa. If a custom implicit conversion exist from C to a type A then the compiler will accept an implicit conversion from T to A. If a custom explicit conversion exists from A to C then the compiler will accept an explicit conversion from A to T.

Casting and generic arrays

If T is a parameter type of a generic class and if T is constrained to derive from C then the C#2 compiler will accept to:

Cast implicitly an array of T into an array of C. In other words, the C#2 compiler accepts to cast implicitly a reference of type T[] into a reference of C[]. We say that the C# arrays accept covariance on their elements.

Cast explicitly an array of C into an array of T. In other words, the C#2 compiler accepts to explicitly cast a reference of type C[] to a reference of type T[]. We say that the C# arrays accept contravariance on their elements.

There is no equivalent rule if T is constrained to implement the I interface. Also, the covariance and the contravariance are not supported on the parameter types of a generic class. In other words, if class D derives from class B, there exists no implicit conversion between a reference of type List<D> and a reference of type List<B>.

is and as operators

To avoid an exception of type InvalidCastException when you are not certain of a type conversion implicating a T type parameter, it is recommended to use the is operator to test if the conversion is possible and the as operator to attempt the conversion. Remember that the as operator returns null if the conversion is not possible. For example:

Inheritance and generics

Basic rules

A non-generic class can inherit from a generic class. In this case, all parameter types must be resolved:

class B<T> {...}
class D : B<double> {...}

A generic class can derive from a generic class. In this case, it is optional to resolve all the parameters. However, it is necessary to repeat the constraints on the non-resolved parameter types. For example:

Finally, know that a generic class can inherit from a non-generic class.

Overriding virtual methods of generic types

A generic base class can have abstract or virtual methods which uses or not parameter types in their signatures. In this case, the compiler forces the methods to be rewritten in derived classes to use the proper parameters. For example:

We take advantage of this example to underline the fact that a generic class can also be abstract. This example also shows the type of compiler error that we will encounter when we do not properly use the parameter types.

It is interesting to note that the parameter types of a derived generic class can be used in the body of an overloaded virtual method, even if the base class is not generic.

C# Generics Part 3/4: Casting, Inheritance, and Generic Methods

Generic methods

Introduction

Whether that it is defined in a generic type or not, that it is static or not, a method has the possibility of defining it own parameter types. Each time such a method is invoked, a type must be provided for each parameter type. Here we talk about the concept of generic method.

The parameter types specific to a method can only be used within the scope of the method (i.e. the return value + signature + body of the method). In the C2<T> class of the following example, there is no correlation between the U parameter type of the Fct<U>() method and the U parameter type of the FctStatic<U>() method.

The parameter types of a method can have the same name as a parameter type for the class defining the method. In this case, the parameter type of the class is hidden within the scope of the method. In the C3<T>.Fct<T>() method of the following example, the T parameter type defined by the methods hides that T parameter type defined by the class. This practice can lead to confusion and the compiler will emit a warning when it is detected.

Of course, a generic method cannot override the constraints on a parameter type defined on its class.

Virtual generic methods

Abstract, virtual and interface methods can also be generic. In this case, the overloading of such methods does not need to respect the name of the parameter types. When overriding a virtual or abstract generic method which has constraints on its parameter types, you must not rewrite this set of constraints. When implementing an interface method which has constraints on its parameter types, you must rewrite this set of constraints. These rules are illustrated by the following example which compiles without warning or errors:

Inference of generic method parameter types

When a generic method is invoked, the C#2 compiler has the possibility of inferring the parameter types based on the types of the provided arguments to the method. Explicitly providing the parameter types for a generic method overrides the inference rules.

The inference rule does not take into account the type of the return value. However, the compiler is capable of inferring a parameter type from the elements of an array. This is illustrated by the following program:

C#2 grammar ambiguity

There is an ambiguity in the C#2 grammar as the lesser than '<' and greater than '>' characters can be used, in certain special cases, both for the definition of the list of parameter types and as well as comparison operators. This special case is illustrated in the following example:

The rule is that when the compiler is faced with this ambiguity, it analyzes the character located right after '>'. If this character is in the following list, then the compiler will infer a list of parameter types:

About the Author

Patrick Smacchia

Patrick Smacchia is a .NET MVP involved in software development for over 15 years. He is the author of Practical .NET2 and C#2 (http://www.PracticalDOT.NET), a .NET book conceived from real world experience with 647 compilable code listings. After graduating in mathematics and computer science, he has worked on software in a variety of fields including stock exchange at Societe Generale, airline ticket reservation system at Amadeus as well as a satellite base station at Alcatel. He's currently a software consultant and trainer on .NET technologies as well as the author of the freeware NDepend which provides numerous metrics and caveats on any compiled .NET (http://www.NDepend.com) application.

Comments

There are no comments yet. Be the first to comment!

You must have javascript enabled in order to post comments.

Leave a Comment

Your email address will not be published. All fields are required.

Name

Email

Title

Comment

Top White Papers and Webcasts

This white paper examines the economics of deploying Red Hat's Storage Server. Based on GlusterFS, a distributed file system that Red Hat acquired as part of Gluster, Red Hat Storage Server is ushering in a new era of software-based storage (also known as software-defined storage by many suppliers) solutions. Such solutions leverage commodity x86-based hardware from server vendors and a distributed shared nothing architecture that allows businesses to build out a service-based storage infrastructure in an …

Data integrity and ultra-high performance dictate the success and growth of many companies.
One of these companies is BridgePay Network Solutions, a recently launched and rapidly growing financial services organization that allows merchants around the world to process millions of daily credit card transactions. Due to the nature of their business, their IT team needed to strike the perfect balance between meeting regulatory-mandated data security measures with the lowest possible levels of latency and …