when the client declares a variable of type linkedlist providing a concrete type for the list's key, the
client-side compiler will insist that the key type is derived from icomparable<t> (with the key's type
as the type parameter) and will refuse to build the client code otherwise.
in general, only define a constraint at the level where you require it. in the linked list example, it is
pointless to define the icomparable<t> derivation constraint at the node level, because the node itself
does not compare keys. if you do so, you will have to place the constraint at the linkedlist level as well,
even if the list does not compare keys. this is because the list contains a node as a member variable,
causing the compiler to insist that the key type defined at the list level complies with the constraint
placed by the node on the generic key type.
in other words, if you define the node as so:
class node<k,t> where k : icomparable<k>
{...}
then you would have to repeat the constraint at the list level, even if you do not provide the find()
method, or any other method for that matter:
public class linkedlist<keytype,datatype> where keytype :
icomparable<keytype>
{
node<keytype,datatype> m_head;
}
you can constrain multiple interfaces on the same generic type parameter, separated by a comma. for
example:
public class linkedlist<k,t> where k : icomparable<k>,iconvertible
{...}
you can provide constraints for every generic type parameter your class uses, for example:
public class linkedlist<k,t> where k : icomparable<k>
where t : icloneable
{...}
you can have a base class constraint, meaning, stipulating that the generic type parameter is derived
from a particular base class:
public class mybaseclass
{...}
public class linkedlist<k,t> where k : mybaseclass
{...}
however, at most one base class can be used in a constraint because c# does not support multiple
inheritance of implementation. obviously, the base class you constrain cannot be a sealed class, and the
compiler enforces that. in addition, you cannot constrain system.delegate or system.array as a base
class .
you can constrain both a base class and one or more interfaces, but the base class must appear first in
the derivation constraint list:
public class linkedlist<k,t> where k : mybaseclass, icomparable<k>
{...}
c# does not allow you to specify a naked generic type parameter as a constraint:
public class linkedlist<k,t,u> where k : u //will not compile
{...}
however, c# does let you use another generic type as a constraint:
public interface isomeinterface<t>
{...}
public class linkedlist<k,t> where k : isomeinterface<int>
{...}
when constraining anther generic type as a base type, you can keep that type generic by specifying the
generic type parameters of your own type parameter. for example, in the case of a generic interface
constraint:
public class linkedlist<k,t> where k : isomeinterface<t>
{...}
or a generic base class constraint:
public class mysubclass<t> where t : mybaseclass<t>
{...}
finally, note that when providing a derivation constraint, the base type (interface or base class) you
constrain must have consistent visibility with that of the generic type parameter you define. for instance,
the following constraint is valid, because internal types can use public types:
public class mybaseclass
{}
internal class mysubclass<t> where t : mybaseclass
{}
however, if the visibility of the two classes were reversed, such as:
internal class mybaseclass
{}
public class mysubclass<t> where t : mybaseclass
{}
then the compiler would issue an error, because no client from outside the assembly will ever be able to
use the generic type mysubclass, rendering mysubclass in effect as an internal rather than a public
type. the reason outside clients cannot use mysubclass is that to declare a variable of type
mysubclass, they require to make use of a type that derives from the internal type mybaseclass.
constructor constraint
suppose you want to instantiate a new generic object inside a generic class. the problem is the c#
compiler does not know whether the specific type the client will use has a matching constructor, and it
will refuse to compile the instantiation line.
to address this problem, c# allows you to constrain a generic type parameter such that it must support
a public default constructor. this is done using the new() constraint. for example, here is a different way
of implementing the default constructor of the generic node <k,t> from code block 3.
class node<k,t> where t : new()
{
public node()
{
key = k.default;
item = new t();
nextnode = null;