Generic Classes’ Features
When creating generic classes, you might need
some more C# keywords. For example, it is not possible to assign
null to a generic type. In this case,
the keyword default can be used. If the
generic type does not require the features of the Object class, but you need to invoke some specific
methods in the generic class, you can define constraints.
This section discusses the following topics:
-
Default Values
-
Constraints
-
Inheritance
-
Static members
Let’s start this example with a generic document
manager. The document manager is used to read and write documents
from a queue. Start by creating a new Console project named
DocumentManager and add the class
DocumentManager<T>. The method
AddDocument() adds a document to the
queue. The read-only property IsDocumentAvailable returns true if the queue is not
empty.
Default Values
Now you add a GetDocument() method to the DocumentManager<T> class. Inside this method
the type T should be assigned to null.
However, it is not possible to assign null to generic types. The reason is that a generic
type can also be instantiated as a value type, and null is allowed only with reference types. To
circumvent this problem, you can use the default keyword. With the default keyword, null is
assigned to reference types and 0 is
assigned to value types.
|
|
Tip |
The default
keyword has multiple meanings depending on the context where it is
used. The switch statement uses a default for defining the default
case, and with generics the default is used to initialize generic
types either to null or 0 depending on if it is a reference or
value type.
|
Constraints
If the generic class needs to invoke some
methods from the generic type, you have to add constraints. With
the DocumentManager<T>, all the
titles of the documents should be displayed in the DisplayAllDocuments() method.
The Document class
implements the interface IDocument with
the properties Title and Content:
For displaying the documents with the DocumentManager<T> class, you can cast the
type T to the interface IDocument to display the title:
The problem is that doing a cast results in a
runtime exception if the type T does not
implement the interface IDocument.
Instead, it would be better to define a constraint with the
DocumentManager<TDocument> class
that the type TDocument must implement
the interface IDocument. To clarify the
requirement in the name of the generic type, T is changed to TDocument. The where
clause defines the requirement to implement the interface
IDocument:
This way you can write the foreach statement in such a way that the type
T contains the property Title. You get support from Visual Studio
IntelliSense and from the compiler:
In the Main() method the
DocumentManager<T> class is
instantiated with the type Document that
implements the required interface IDocument. Then new documents are added and
displayed, and one of the documents is retrieved:
The DocumentManager now
works with any class that implements the interface IDocument.
In the sample application, you’ve seen an interface
constraint. Generics support several constraint types:
|
|
Tip |
With CLR 2.0 only constructor constraints for
the default constructor can be defined. It is not possible to
define a constructor constraint for other constructors.
|
With a generic type, you can also combine multiple
constraints. The constraint where T :
IFoo, new() with the MyClass<T> declaration specifies that type
T implements the interface IFoo and has a default constructor:
|
|
Important |
One important restriction of the where clause with C# 2.0 is that it’s not possible
to define operators that must be implemented by the generic type.
Operators cannot be defined in interfaces. With the where clause, it is only possible to define base
classes, interfaces, and the default constructor.
|
Inheritance
The LinkedList<T> class created earlier implements
the interface IEnumerable<T>:
A generic type can implement a generic interface.
The same is possible by deriving from a class. A generic class can
be derived from a generic base class:
The requirement is that the generic types of the
interface must be repeated, or the type of the base class must be
specified, as in this case:
This way, the derived class can be a generic or
nongeneric class. For example, you can define an abstract generic
base class that is implemented with a concrete type in the derived
class. This allows you to do specialization for specific types:
Static Members
Static members of generic classes require
special attention. Static members of a generic class are only
shared with one instantiation of the class. Let’s have a look at
one example. The class StaticDemo<T> contains the static field
x:
Because of using the class StaticDemo<T> both with a string type and an int
type, two sets of static fields exist: