Книга: C# 2008 Programmer
Using Constraints in a Generic Type
Разделы на этой странице:
Using Constraints in a Generic Type
Using the MyStack
class, suppose that you want to add a method called Find()
that allows users to check if the stack contains a specific item. You implement the Find()
method like this:
public class MyStack<T> {
private T[] _elements;
private int _pointer;
public MyStack(int size) {
_elements = new T[size];
_pointer = 0;
}
public void Push(T item) {
if (_pointer > _elements.Length - 1) {
throw new Exception("Stack is full.");
}
_elements[_pointer] = item;
_pointer++;
}
public T Pop() {
_pointer--;
if (_pointer < 0) {
return default(T);
//throw new Exception("Stack is empty.");
}
return _elements[_pointer];
}
public bool Find(T keyword) {
bool found = false;
for (int i=0; i<_pointer; i++) {
if (_elements[i] == keyword) {
found = true;
break;
}
}
return found;
}
}
But the code will not compile. This is because of the statement:
if (_elements[i] == keyword)
That's because the compiler has no way of knowing if the actual type of item
and keyword
(type T
) support this operator (see Figure 9-3). For example, you cannot by default compare
two struct objects.
Figure 9-3
A better way to resolve this problem is to apply constraint to the generic class so that only certain data types can be used. In this case, because you want to perform comparison in the Find()
method, the data type used by the generic class must implement the IComparable<T>
interface. This is enforced by using the where
keyword:
public class MyStack<T> where T : IComparable<T> {
private T[] _elements;
private int _pointer;
public MyStack(int size) {
_elements = new T[size];
_pointer = 0;
}
public void Push(T item) {
if (_pointer > _elements.Length - 1) {
throw new Exception("Stack is full.");
}
_elements[_pointer] = item;
_pointer++;
}
public T Pop() {
_pointer--;
if (_pointer < 0) {
return default(T);
}
return _elements[_pointer];
}
public bool Find(T keyword) {
bool found = false;
for (int i=0; i<_pointer; i++) {
if (_elements[i].CompareTo(keyword) == 0) {
found = true;
break;
}
}
return found;
}
}
For the comparison, you use the CompareTo()
method to compare two items of type T
(which must implement the IComparable
interface). The CompareTo()
method returns 0 if the two objects are equal. You can now search for an item by using the Find()
method:
MyStack<string> stack = new MyStack<string>(3);
stack.Push("A");
stack.Push("B");
stack.Push("C");
if (stack.Find("B")) Console.WriteLine("Contains B");
In this case, the code works because the string
type implements the IComparable
interface. Suppose that you have the following Employee
class definition:
public class Employee {
public string ID { get; set; }
public string Name { get; set; }
}
When you try to use the MyStack
class with the Employee
class, you get an error:
MyStack<Employee> stack = new MyStack<Employee>(3); //---Error---
That's because the Employee
class does not implement the IComparable<T>
interface. To resolve this, simply implement the IComparable<Employee>
interface in the Employee
class and implement the CompareTo()
method:
public class Employee : IComparable<Employee> {
public string ID { get; set; }
public string Name { get; set; }
public int CompareTo(Employee obj) {
return this.ID.CompareTo(obj.ID);
}
}
You can now use the Employee
class with the generic MyStack
class:
MyStack<Employee> stack = new MyStack<Employee>(2);
stack.Push(new Employee() { ID = "123", Name = "John" });
stack.Push(new Employee() { ID = "456", Name = "Margaret" });
Employee e1 = new Employee() { ID = "123", Name = "John" };
if (stack.Find(e1))
Console.WriteLine("Employee found.");
Specifying Multiple Constraints
You can specify multiple constraints in a generic type. For example, if you want the MyStack
class to manipulate objects of type Employee
and also implement the IComparable
interface, you can declare the generic type as:
public class MyStack<T> where T : Employee, IComparable<T> {
//...
}
Here, you are constraining that the MyStack
class must use types derived from Employee
and they must also implement the IComparable
interface.
The base class constraint must always be specified first, before specifying the interface.
Assuming that you have the following Manager
class deriving from the Employee
class:
public class Manager : Employee, IComparable<Manager> {
public int CompareTo(Manager obj) {
return base.CompareTo(obj);
}
}
The following statement is now valid:
MyStack<Manager> stackM = new MyStack<Manager>(3);
Multiple Type Parameter
So far you have seen only one type parameter used in a generic type, but you can have multiple type parameters. For example, the following MyDictionary
class uses two generic type parameters — K
and V
:
public class MyDictionary<K, V> {
//...
}
To apply constraints on multiple type parameters, use the where
keyword multiple times:
public class MyDictionary<K, V>
where K : IComparable<K> where V : ICloneable {
//...
}
- Understanding Generics
- Generic Interfaces
- Generic Structs
- Generic Delegates
- Advantages of Generics
- Appendix C. ICMP types
- Caveats using NAT
- Using Double Quotes to Resolve Variables in Strings with Embedded Spaces
- SCTP Generic header format
- SCTP Common and generic headers
- Generic matches
- Addrtype match