Chapter 1. C# 2.0
In this chapter, you will learn about and use the new features in C# 2.0, including generics, iterators, anonymous methods, partial types, static classes, nullable types, and limiting access to properties, as well as delegate covariance and contravariance.
Probably the most exciting and most anticipated new feature in C# 2.0 is generics, which provide you with quick and easy type-safe collections. So, let’s start there.
Create a Type-Safe List Using a Generic Collection
Type safety is the key to creating code that’s easy to maintain. A type-safe language (and framework) finds bugs at compile time (reliably) rather than at runtime (usually after you’ve shipped the product!). The key weakness in C# 1.x was the absence of generics , which enable you to declare a general collection (for example, a stack or a list) that can accept members of any type yet will be type-safe at compile time.
In Version 1.x of the framework, nearly all the collections were
declared to hold instances of System.Object
, and because
everything derives from System.Object
, these collections could hold
any type at all; that is, they were not type-safe.
Suppose, for example, you were creating a list of Employee
objects in C# 1.x. To do so, you
would use an ArrayList
, which holds objects of the System.Object
type. Adding new Employee
s to the collection was not a problem
because Employee
s were derived from
System.Object
, but when you tried to
retrieve an Employee
from the
ArrayList
, all you would get back was
an Object
reference, which you would
then have to cast:
Employee theEmployee = (Employee) myArrayList[1];
An even bigger problem, however, was that there was nothing to
stop you from adding a string or some other type to the ArrayList
. As long as you never needed to
access the string, you would never note the errant type. Suppose,
however, that you passed that ArrayList
to a method that expected an
ArrayList
of Employee
objects. When that method attempted
to cast the String
object to the
Employee
type at runtime, an
exception would be thrown.
A final problem with .NET 1.x collections arose when you added value types to the collection. Value types had to be boxed on their way into the collection and explicitly unboxed on their way out.
.NET 2.0 eliminates all these problems with a new library of
collections, which you will find in the System.Collections.Generic
namespace. A generic
collection is simply a collection that allows you to specify its
member types when you declare it. Once declared, the compiler will allow
only objects of that type to be added to your list. You define generic
collections using special syntax; the syntax uses angle brackets to
indicate variables that must be defined when an instance of the
collection is declared.
There is no need to cast when you retrieve objects from a generic
collection, and your code is safer, easier to maintain, and simpler to
use than it is with untyped collections such as ArrayList
.
How do I do that?
To get a feel for the new generic types in .NET 2.0, let’s use
the type-safe List
class to create a list of employees. To execute this
lab, open Visual Studio 2005, create a new C# Console application, and
name it CreateATypeSafeList
.
Replace the code Visual Studio 2005 creates for you with the code in
Example 1-1.
Tip
You must use the System.Collections.Generic
namespace to
use the generic types. By default Visual Studio 2005 adds this
namespace to all projects.
using System;
using System.Collections.Generic;
namespace CreateATypeSafeList
{
// a class to store in the List
public class Employee
{
private int empID;
// constructor
public Employee(int empID)
{
this.empID = empID;
}
// override the ToString method to
// display this employee's id
public override string ToString( )
{
return empID.ToString( );
}
} // end class
// Test driver class
public class Program
{
// entry point
static void Main( )
{
// Declare the type safe list (of Employee objects)
List<Employee> empList = new List<Employee>( );
// Declare a second type safe list (of integers)
List<int> intList = new List<int>( );
// populate the Lists
for (int i = 0; i < 5; i++)
{
empList.Add(new Employee(i + 100));
intList.Add(i * 5);
// empList.Add(i * 5); // see "What About" section below
}
// print the integer list
foreach (int i in intList)
{
Console.Write("{0} ", i.ToString( ));
}
Console.WriteLine("\n");
// print the Employee List
foreach (Employee employee in empList)
{
Console.Write("{0} ", employee.ToString( ));
}
Console.WriteLine("\n");
}
}
}
Output:
0 5 10 15 20 100 101 102 103 104
Tip
All the source code for the labs in this chapter is available on my web site, http://www.LibertyAssociates.com. Click Books, and then scroll down to C# 2.0 Programmer’s Notebook and click Source to save the source code to your computer.
Once unzipped, the source code is in chapter folders, and each lab folder is named with the namespace shown in the listing. For instance, for Example 1-1, the source is stored in Chapter 1\ CreateATypeSafeList.
While you are at my site, you can also read the FAQ list and errata sheet and join a private support discussion forum.
What just happened?
This listing creates two classes: an Employee
class to be held in the collection
and the Program
class created by
Visual Studio 2005. It also uses the List
class provided by the .NET Framework
Class Library.
The Employee
class contains a
single private field (empID
), a
constructor, and an override of ToString
to return the empID
field as a string.
First you create an instance of List
that will hold Employee
objects. The type of empList
is “List of Employee Objects” and is
declared thus:
List<Employee> empList
When you see the definition List<T>
, the T
is a placeholder for the actual type
you’ll place in that list.
As always, empList
is just a
reference to the object you create on the heap using the new
keyword. The new
keyword expects you to invoke a constructor, which you
do as follows:
new List<Employee>( )
This creates an instance of “List of Employee Objects” on the
heap, and the entire statement, put together, assigns a reference to
that new object to empList
:
List<Employee> empList = new List<Employee>( );
Tip
This is just like writing:
Dog milo = new Dog( );
in which you create an instance of Dog
on the heap and assign it to the
reference to Dog
, milo
.
In the next statement, you create a second List
, this time of type “List of
Integers”:
List
<int> intList = newList
<int>( );
Now you are free to add integers to the list of integers, and
Employee
objects to the list of
Employee
objects. Once the lists
are populated, you can iterate through each of them, using a foreach
loop to display their contents in
the console window:
foreach (Employee
employee in empList) {Console
.Write("{0} ", employee.ToString( )); }
What about...
...if you try to add an integer to the list of Employee
s?
Try it. Start by uncommenting the following line in Example 1-1 and recompiling the program:
empList.Add(i * 5);
You’ll get a pair of compile errors:
Error 1 The best overloaded method match for 'System.Collections. Generic.List<ListCollection.Employee>.Add(ListCollection.Employee)' has some invalid arguments Error 2 Argument '1': cannot convert from 'int' to 'ListCollection.Employee'
The information provided in these two compile errors enable you
to determine that it is not legal to add an int
to a collection of Employee
objects because no implicit
conversion or subtype relationship exists from one to the
other.
The good news is that this is a compile error, rather than a runtime error, which can sneak out the door and happen only when your client runs the application!
Note
You can store derived types in a type-safe collection. Thus, a collection of Employees will hold a Manager object, if Manager derives from Employee.
...what about other generic collections; are any available?
Other generic collections are available as well. For instance,
the Stack and Queue collections, as well as the ICollection
interface, are available in type-safe versions in .NET
2.0.
You use these just as you would List<T>
. For example, to make a stack
of Employee
objects, you replace
T
in the Stack
definition (Stack<T>
) with the Employee
type:
Stack<Employee> employeeStack = new Stack<Employee>( );
Where can I learn more?
You can learn about all the .NET 2.0 generic classes in the MSDN topic titled “Commonly Used Collection Types,” and you can read an article on the subject on O’Reilly’s ONDotnet.com site at http://www.ondotnet.com/pub/a/dotnet/2004/05/17/liberty.html.
Tip
A document on my web site lists the links I mention in each lab so that you can copy and paste them into your browser. To get it, go to http://www.LibertyAssociates.com, click Books, scroll down to Visual C# 2005: A Developer’s Notebook, and click Links.doc.
The next lab will show you how to create your own type-safe collections to supplement those provided by the Framework.
Create Your Own Generic Collection
.NET 2.0 provides a number of generic collection classes for lists, stacks, queues, dictionaries, etc. Typically, these are more than sufficient for your programming needs. But from time to time you might decide to create your own generic collection classes, such as when you want to provide those collections with problem-specific knowledge or capabilities that are simply not available in existing collections (for example, creating an optimized linked list, or adding generic collection semantics to another class you’ve created). It is a goal of the language and the Framework to empower you to create your own generic collection types.
How do I do that?
The easiest way to create a generic collection class is to
create a specific collection (for example, one that holds integers)
and then replace the type (for example, int
) with the generic type (for example,
T
).
Thus:
private int data;
becomes:
private T data; // T is a generic Type Parameter
The generic type parameter (in this case, T
) is defined by you when you create your
collection class by placing the type parameter inside angle brackets
(< >
):
public class Node<T>
Tip
Many programmers use T
for
“type,” but Microsoft recommends you use longer, more descriptive
type names (for example, Node<DocumentType>
).
Now you have defined a new type, “Node of T
,” which at runtime will become “Node of
int
" or node of any other type the
compiler recognizes.
Example 1-2 creates
a linked list of nodes of T
, and
then uses two instances of that generic list, each holding a different
type of object.
using System; namespace GenericLinkedList { public class Pilgrim { private string name; public Pilgrim(string name) { this.name = name; } public override string ToString( ) { return this.name; } } public class Node<T> { // member fields private T data; private Node<T> next = null; // constructor public Node(T data) { this.data = data; } // properties public T Data { get { return this.data; } } public Node<T> Next { get { return this.next; } } // methods public void Append(Node<T> newNode) { if (this.next = = null) { this.next = newNode; } else { next.Append(newNode); } } public override string ToString( ) { string output = data.ToString( ); if (next != null) { output += ", " + next.ToString( ); } return output; } } // end class public class LinkedList<T> { // member fields private Node<T> headNode = null; // properties // indexer public T this[int index] { get { int ctr = 0; Node<T> node = headNode; while (node != null && ctr <= index) { if (ctr = = index) { return node.Data; } else { node = node.Next; } ++ctr; } // end while throw new ArgumentOutOfRangeException( ); } // end get } // end indexer // constructor public LinkedList( ) { } // methods public void Add(T data) { if (headNode = = null) { headNode = new Node<T>(data); } else { headNode.Append(new Node<T>(data)); } } public override string ToString( ) { if (this.headNode != null) { return this.headNode.ToString( ); } else { return string.Empty; } } } class Program { static void Main(string[ ] args) { LinkedList<int> myLinkedList = new LinkedList<int>( ); for (int i = 0; i < 10; i++) { myLinkedList.Add(i); } Console.WriteLine("Integers: " + myLinkedList); LinkedList<Pilgrim> pilgrims = new LinkedList<Pilgrim>( ); pilgrims.Add(new Pilgrim("The Knight")); pilgrims.Add(new Pilgrim("The Miller")); pilgrims.Add(new Pilgrim("The Reeve")); pilgrims.Add(new Pilgrim("The Cook")); Console.WriteLine("Pilgrims: " + pilgrims); Console.WriteLine("The fourth integer is " + myLinkedList[3]); Pilgrim d = pilgrims[1]; Console.WriteLine("The second pilgrim is " + d); } } }
Output:
Integers: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 Pilgrims: The Knight, The Miller, The Reeve, The Cook The fourth integer is 3 The second pilgrim is The Miller
What just happened?
You just created a generic linked list; one
that is type-safe for any type of object you hold in the collection.
In fact, one way to create a linked list such as this is to start by
creating a type-specific linked list. This simple example works by
defining a generic linked list whose head node is initialized to
null
:
public classLinkedList
<T> { privateNode
<T> headNode = null; ... }
When you add data to the linked list, a new node is created and
if there is no head node, that new node becomes the head; otherwise,
append
is called on the head
node.
Each node checks to see if its next field is null (and thus the current node is the end of the list). If so, the current node appends the new node; otherwise, it passes the new node to the next member in the list.
Note
Creating collections with generics is far easier than you might imagine. The simplest way to approach the problem is to build a type-specific collection, and then replace the type with the generic <T>.
Notice that LinkedList
is
intentionally declared with the same generic type parameter as
Node
. Because they both use the
same letter (T
), the compiler knows
that the type used to substitute for T
in LinkedList
will be the same type used to
substitute for T
in Node
. This makes sense: a linked list of
integers will hold nodes of integers.
What about...
...using generics with other code structures? Can I do that?
Sure; you also can use generics with structs, interfaces, delegates, and even methods.
Where can I learn more?
For more about creating your own class with generics, see the MSDN Help file, “Topic: Generics,” as well as the article mentioned previously on O’Reilly’s ONDotnet.com site at http://www.ondotnet.com/pub/a/dotnet/2004/05/17/liberty.html. Also, an open “Community Project to Develop the Best Public License Collection Classes for .NET” is available on the Wintellect site at http://www.wintellect.com/powercollections/.
Implement the Collection Interfaces
In addition to its generic collection classes, .NET 2.0 also
provides a set of generic interfaces that enable you to create type-safe
collections that have all the functionality of the earlier, nongeneric
.NET 1.x collection types. You’ll find these interfaces in the System.Collections.Generic
namespace. The namespace also includes a number of
related generic interfaces, such as IComparable<T>
, which you can use to compare two objects of type
T
regardless of whether they are part
of a collection.
You can create a sorted linked list by having each datatype stored in the
list implement the IComparable<T>
interface and by having
your Node
object be responsible for
inserting each new Node
at the
correct (sorted) position in the linked list.
How do I do that?
Integer
already implements
IComparable
; you can easily modify
Pilgrim
to do so as well. Modify
the definition of the Pilgrim
class
to indicate that it implements the IComparable<T>
interface:
public class Pilgrim : IComparable<Pilgrim>
Be sure to implement the CompareTo
and the Equals
methods that the interface requires.
The objects these methods receive will be of type Pilgrim
because this is a type-safe
interface, not a “standard” interface that would pass in
objects:
public int CompareTo(Pilgrim
rhs)
public bool Equals(Pilgrim rhs)
All you need to do now is change the logic of adding a node.
This time, instead of adding to the end of the list, you’ll insert the
new node into the list where it belongs based on the implementation of
the CompareTo
method.
For this to work, you must ensure that the datatype held in the
node implements IComparable
. You
accomplish this with a constraint using the keyword where
:
public classNode
<T> :IComparable
<Node
<T>> where T:IComparable<T>
This line of code declares a class Node
of T
that implements IComparable
(of
Node
of T
) and that is constrained to hold datatypes
that implement IComparable
. If you
try to have your Node
class hold an
object that does not implement IComparable
, you will receive an error
message when you attempt to compile it.
You must be careful to return the new head of the list if the new node is “less than” the current head of the list, as shown in Example 1-3 (Changes from the previous example are highlighted.)
using System; using System.Collections.Generic; namespace ImplementingGenericInterfaces { public class Pilgrim :IComparable<Pilgrim>
{ private string name; public Pilgrim(string name) { this.name = name; } public override string ToString( ) { return this.name; } // implement the interface public int CompareTo(Pilgrim rhs) { return this.name.CompareTo(rhs.name); } public bool Equals(Pilgrim rhs) { return this.name = = rhs.name; } } // node must implement IComparable of Node of T // constrain Nodes to only take items that implement Icomparable // by using the where keyword. public class Node<T> : IComparable<Node<T>> where T:IComparable<T> { // member fields private T data; private Node<T> next = null;private Node<T> prev = null;
// constructor public Node(T data) { this.data = data; } // properties public T Data { get { return this.data; } } public Node<T> Next { get { return this.next; } } public int CompareTo(Node<T> rhs) { // this works because of the constraint return data.CompareTo(rhs.data); } public bool Equals(Node<T> rhs) { return this.data.Equals(rhs.data); } // methods public Node<T> Add(Node<T> newNode) { if (this.CompareTo(newNode) > 0) // goes before me { newNode.next = this; // new node points to me // if I have a previous, set it to point to // the new node as its next if (this.prev != null) { this.prev.next = newNode; newNode.prev = this.prev; } // set prev in current node to point to new node this.prev = newNode; // return the newNode in case it is the new head return newNode; } else // goes after me { // if I have a next, pass the new node along for comparison if (this.next != null) { this.next.Add(newNode); } // I don't have a next so set the new node // to be my next and set its prev to point to me. else { this.next = newNode; newNode.prev = this; } return this; } } public override string ToString( ) { string output = data.ToString( ); if (next != null) { output += ", " + next.ToString( ); } return output; } } // end class public class
SortedLinkedList<T> where T : IComparable<T>
{ // member fields private Node<T> headNode = null; // properties // indexer public T this[int index] { get { int ctr = 0; Node<T> node = headNode; while (node != null && ctr <= index) { if (ctr = = index) { return node.Data; } else { node = node.Next; } ++ctr; } // end while throw new ArgumentOutOfRangeException( ); } // end get } // end indexer // constructor publicSortedLinkedList
( ) { } // methods public void Add(T data) { if (headNode = = null) { headNode = new Node<T>(data); } else { headNode =headNode.Add(new Node<T>(data));
} } public override string ToString( ) { if (this.headNode != null) { return this.headNode.ToString( ); } else { return string.Empty; } } } class Program { // entry point static void Main(string[ ] args) { SortedLinkedList<int> mySortedLinkedList = new SortedLinkedList<int>( ); Random rand = new Random( ); Console.Write("Adding: "); for (int i = 0; i < 10; i++) { int nextInt = rand.Next(10); Console.Write("{0} ", nextInt); mySortedLinkedList.Add(nextInt); } SortedLinkedList<Pilgrim> pilgrims = new SortedLinkedList<Pilgrim>( ); pilgrims.Add(new Pilgrim("The Knight")); pilgrims.Add(new Pilgrim("The Miller")); pilgrims.Add(new Pilgrim("The Reeve")); pilgrims.Add(new Pilgrim("The Cook")); pilgrims.Add(new Pilgrim("The Man of Law")); Console.WriteLine("\nRetrieving collections..."); DisplayList<int>("Integers", mySortedLinkedList); DisplayList<Pilgrim>("Pilgrims", pilgrims); //Console.WriteLine("Integers: " + mySortedLinkedList); //Console.WriteLine("Pilgrims: " + pilgrims); Console.WriteLine("The fourth integer is " + mySortedLinkedList[3]); Pilgrim d = pilgrims[2]; Console.WriteLine("The third pilgrim is " + d); // foreach (Pilgrim p in pilgrims) // { // Console.WriteLine("The pilgrim's name is " + p.ToString( )); // } } // end main private static void DisplayList<T>(string intro, SortedLinkedList<T> theList) where T : IComparable<T> { Console.WriteLine(intro + ": " + theList); } } // end class } // end namespace
Output:
Adding: 2 8 2 5 1 7 2 8 5 5 Retrieving collections... Integers: 1, 2, 2, 5, 7, 8, 8 Pilgrims: The Cook, The Knight, The Man of Law, The Miller, The Reeve The fourth integer is 5 The third pilgrim is The Man of Law
What just happened?
The Pilgrim
class changed
just enough to implement the generic IComparable
interface. The linked list
didn’t change at all, but the Node
class did undergo some changes to support the sorted list.
First, the Node
class was
marked to implement IComparable
and
was constrained to hold only objects that themselves implement
IComparable
:
public classNode
<T> :IComparable
<Node
<T>> where T:IComparable<T>
Second, Node
added a
reference to the previous node, in addition to the next node (making
this a doubly linked list):
privateNode
<T> next = null; privateNode
<T> prev = null;
The Node
class must implement
CompareTo
and Equals
. These are simple to implement
because the constraint ensures that the data you are comparing also
implements IComparable
:
public int CompareTo(Node
<T> rhs)
{
// this works because of the constraint
data.CompareTo(rhs.data);
}
What about . . .
...the IComparable
requirement? Why did Pilgrim
and
Node
require IComparable
, but the linked list did
not?
To understand this, it’s important to note that both Pilgrim
s and Node
s must be compared; linked lists are not
compared. Because the linked list is sorted by sorting its nodes,
there is no need to compare two linked lists to see which one is
“greater” than the other.
...what about passing generic types to a method; can I do that?
Yes, you can pass a generic type to a method, but only if the method is generic. In Example 1-3, you display the contents of the list of integers and the list of pilgrims with the following code:
Console.WriteLine("Integers: " + myLinkedList); Console.WriteLine("Pilgrims: " + pilgrims);
You are free to create a method to take these lists and display them (or manipulate them):
private void DisplayList<T>(string intro,LinkedList
<T> theList) where T : IComparable<T> {Console
.WriteLine(intro + ": " + theList); }
When you call the method, you supply the type:
DisplayList<int>("Integers", myLinkedList);
DisplayList<Pilgrim
>("Pilgrims", pilgrims);
Tip
The compiler is capable of type inference, so you can rewrite the preceding two lines as follows:
DisplayList("Integers", myLinkedList); DisplayList("Pilgrims", pilgrims);
Where can I learn more?
The MSDN Library covers the Generic
namespace extensively. Search on
Systems.Collections.Generic. Also, see my article on generics on
O’Reilly’s ONDotnet.com site at http://www.ondotnet.com/pub/a/dotnet/2004/05/17/liberty.html.
Enumerate Using Generic Iterators
In the previous examples you could not iterate over your list of Pilgrim
s using a foreach
loop. As such, if you try to use the
following code in Example
1-3:
foreach (Pilgrim
p in pilgrims ) {Console
.WriteLine("The pilgrim's name is " + p.ToString( )); }
you will receive the following error:
Error 1 foreach statement cannot operate on variables of type 'ImplementingGenericInterfaces.LinkedList <ImplementingGenericInterfaces.Pilgrim>' because 'ImplementingGenericInterfaces.LinkedList <ImplementingGenericInterfaces. Pilgrim>' does not contain a public definition for 'GetEnumerator'
In earlier versions of C#, implementing GetEnumerator
was somewhat complicated and
always tedious, but in C# 2.0 it is greatly simplified.
How do I do that?
To simplify the process of creating iterators, we’ll begin by
simplifying both the Pilgrim
class
and the Linked List
class. The
Linked List
class will forgo all
use of nodes and will store its contents in a fixed-size array (as the
simplest type-safe container imaginable). Thus, it is a Linked List
in name only! This will allow us
to focus on the implementation of the IEnumerator
interface, as shown in Example 1-4.
#region Using directives using System; using System.Collections.Generic; using System.Text; #endregion namespace SimplifiedEnumerator { // simplified Pilgrim public class Pilgrim { private string name; public Pilgrim(string name) { this.name = name; } public override string ToString( ) { return this.name; } } // simplified Linked List class NotReallyALinkedList<T> : IEnumerable<T> { // the entire linked list is stored in this // fixed size array T[ ] myArray; // constructor takes an array and stores the members public NotReallyALinkedList(T[ ] members) { myArray = members; } // implement the method for IEnumerable IEnumerator<T> IEnumerable<T>.GetEnumerator( ) { foreach (T t in this.myArray) { yield return t; } } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator( ) { throw new NotImplementedException( ); } } class Program { static void Main(string[ ] args) { // hardcode a string array of Pilgrim objects Pilgrim[ ] pilgrims = new Pilgrim[5]; pilgrims[0] = new Pilgrim("The Knight"); pilgrims[1] = new Pilgrim("The Miller"); pilgrims[2] = new Pilgrim("The Reeve"); pilgrims[3] = new Pilgrim("The Cook"); pilgrims[4] = new Pilgrim("The Man Of Law"); // create the linked list, pass in the array NotReallyALinkedList<Pilgrim> pilgrimCollection = new NotReallyALinkedList<Pilgrim>(pilgrims); // iterate through the linked list foreach (Pilgrim p in pilgrimCollection) { Console.WriteLine(p); } } } }
Output:
The Knight The Miller The Reeve The Cook The Man Of Law
What just happened?
In this example, the linked list is greatly simplified to keep
its members in an array (in fact, it is not really a linked list at
all). Because you’ve made your pseudo-LinkedList
enumerable, however, now you can
enumerate the Pilgrim
s in the
pilgrims
collection using a
foreach
loop.
When you write:
foreach (Pilgrim
p in pilgrimCollection)
the C# compiler invokes the GetEnumerator
method of the class.
Internally, it looks more or less like this:
Enumerator e = pilgrimCollection.GetEnumerator( ); while (e.MoveNext( )) { Pilgrim p = e.Current; }
As noted earlier, in C# 2.0 you do not have to worry about
implementing MoveNext( )
or the
current property. You need only use the new C# keyword yield
.
Note
Whenever you call foreach, the compiler internally translates it to a call to GetEnumerator.
You use yield
only in
iterator blocks. It either provides a value to the enumerator object
or it signals the end of the iteration:
yield return expression; yield break;
If you step into the foreach
loop with the debugger, you’ll find that each time through the
foreach
loop, the GetEnumerator
method of the linked list is
called, and each time through the next member in the array, it is
yielded back to the calling foreach
loop.
What about...
...implementing the GetEnumerator
method on a more complex data
structure, such as our original LinkedList
?
That is shown in the next lab.
Implement GetEnumerator with Complex Data Structures
To add an iterator to your original LinkedList
class, you’ll implement IEnumerable<T>
on both LinkedList
and the Node
class:
public classLinkedList
<T> :IEnumerable
<T> public class Node<T> : IComparable<Node<T>>, IEnumerable<Node<T>>
How do I do that?
As noted in the previous lab, the IEnumerable
interface requires that you
implement only one method, GetEnumerator
, as shown in Example 1-5. (Changes from
Example 1-3 are
highlighted.)
using System;
using System.Collections.Generic;
namespace GenericEnumeration
{
public class Pilgrim : IComparable<Pilgrim>
{
private string name;
public Pilgrim(string name)
{
this.name = name;
}
public override string ToString( )
{
return this.name;
}
// implement the interface
public int CompareTo(Pilgrim rhs)
{
return this.name.CompareTo(rhs.name);
}
public bool Equals(Pilgrim rhs)
{
return this.name = = rhs.name;
}
}
// node must implement IComparable of Node of T
// node now implements IEnumerable allowing its use in a foreach loop
public class Node<T> : IComparable<Node<T>>,
IEnumerable<Node<T>> where T:IComparable<T>
{
// member fields
private T data;
private Node<T> next = null;
private Node<T> prev = null;
// constructor
public Node(T data)
{
this.data = data;
}
// properties
public T Data { get { return this.data; } }
public Node<T> Next
{
get { return this.next; }
}
public int CompareTo(Node<T> rhs)
{
return data.CompareTo(rhs.data);
}
public bool Equals(Node<T> rhs)
{
return this.data.Equals(rhs.data);
}
// methods
public Node<T> Add(Node<T> newNode)
{
if (this.CompareTo(newNode) > 0) // goes before me
{
newNode.next = this; // new node points to me
// if I have a previous, set it to point to
// the new node as its next
if (this.prev != null)
{
this.prev.next = newNode;
newNode.prev = this.prev;
}
// set prev in current node to point to new node
this.prev = newNode;
// return the newNode in case it is the new head
return newNode;
}
else // goes after me
{
// if I have a next, pass the new node along for comparison
if (this.next != null)
{
this.next.Add(newNode);
}
// I don't have a next so set the new node
// to be my next and set its prev to point to me.
else
{
this.next = newNode;
newNode.prev = this;
}
return this;
}
}
public override string ToString( )
{
string output = data.ToString( );
if (next != null)
{
output += ", " + next.ToString( );
}
return output;
}
// Method required by IEnumerable
IEnumerator<Node<T>> IEnumerable<Node<T>>.GetEnumerator( )
{
Node<T> nextNode = this;
// iterate through all the nodes in the list
// yielding each in turn
do
{
Node<T> returnNode = nextNode;
nextNode = nextNode.next;
yield return returnNode;
} while (nextNode != null);
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator( )
{
throw new NotImplementedException( );
}
} // end class
// implements IEnumerable so that you can use a LinkedList
// in a foreach loop
public class LinkedList<T> : IEnumerable<T> where T : IComparable<T>
{
// member fields
private Node<T> headNode = null;
// properties
// indexer
public T this[int index]
{
get
{
int ctr = 0;
Node<T> node = headNode;
while (node != null && ctr <= index)
{
if (ctr = = index)
{
return node.Data;
}
else
{
node = node.Next;
}
++ctr;
} // end while
throw new ArgumentOutOfRangeException( );
} // end get
} // end indexer
// constructor
public LinkedList
( )
{
}
// methods
public void Add(T data)
{
if (headNode = = null)
{
headNode = new Node<T>(data);
}
else
{
headNode = headNode.Add(new Node<T>(data));
}
}
public override string ToString( )
{
if (this.headNode != null)
{
return this.headNode.ToString( );
}
else
{
return string.Empty;
}
}
// Implement IEnumerable required method
// iterate through the node (which is enumerable)
// and yield up the data from each node returned
IEnumerator<T> IEnumerable<T>.GetEnumerator( )
{
foreach (Node<T> node in this.headNode)
{
yield return node.Data;
}
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator( )
{
throw new NotImplementedException( );
}
}
class Program
{
private static void DisplayList<T>(string intro, LinkedList<T> theList)
where T : IComparable<T>
{
Console.WriteLine(intro + ": " + theList);
}
// entry point
static void Main(string[ ] args)
{
LinkedList<Pilgrim> pilgrims = new LinkedList<Pilgrim>( );
pilgrims.Add(new Pilgrim("The Knight"));
pilgrims.Add(new Pilgrim("The Miller"));
pilgrims.Add(new Pilgrim("The Reeve"));
pilgrims.Add(new Pilgrim("The Cook"));
pilgrims.Add(new Pilgrim("The Man of Law"));
DisplayList<Pilgrim>("Pilgrims", pilgrims);
Console.WriteLine("Iterate through pilgrims...");
// Now that the linked list is enumerable, we can put
// it into a foreach loop
foreach (Pilgrim p in pilgrims)
{
Console.WriteLine("The pilgrim's name is " + p.ToString( ));
}
}
}
}
Output:
Pilgrims: The Cook, The Knight, The Man of Law, The Miller, The Reeve Iterate through pilgrims... The pilgrim's name is The Cook The pilgrim's name is The Knight The pilgrim's name is The Man of Law The pilgrim's name is The Miller The pilgrim's name is The Reeve
What just happened?
The linked list implements its enumerator to call foreach
on the head node (which you can do
because Node
also implements
IEnumerable
). Then you yield the
data object you get back from the node:
IEnumerator
<T>IEnumerable
<T>.GetEnumerator( ) { foreach (Node
<T> node in this.headNode) { yield return node.Data; } }
This gives Node
the
responsibility of iterating through the node list, which is
accomplished, once again, using the yield
statement in its own GetEnumerator
method.
Note
When you use the yield statement, the C# compiler automatically generates a nested implementation of IEnumerator for you. It keeps its own state; you simply tell it which value to yield.
IEnumerator<Node<T>> IEnumerable<Node<T>>.GetEnumerator( ) {Node
<T> nextNode = this; do {Node
<T> returnNode = nextNode; nextNode = nextNode.next; yield return returnNode; } while (nextNode != null); }
You initialize nextNode
to
the current node, and then you begin your do...while
loop. This is guaranteed to run
at least once. returnNode
is set to
nextNode
, and then, once that is
stashed away, nextNode
is set to
its next node (that is, the next node in the
list). Then you yield returnNode
.
Each time through you are returning the next node in the list until
nextNode
is null, at which time you
stop.
What about...
...the fact that in LinkedList
you asked for each Node<T>
in headNode
? Is headNode
a collection?
Actually, headNode
is the top
node in a linked list. Because Node
implements IEnumerable
, the node is
acting like a collection. This isn’t as arbitrary as it sounds because
a node acts as a collection in the sense that it can give you the next
node in the list. You could redesign the linked list to make the nodes
“dumber” and the list itself “smarter,” in which case it would be the
list’s job to iterate over each node in turn.
Simplify Your Code with Anonymous Methods
Anonymous methods allow you to define method blocks inline. In general, you can use anonymous methods anywhere you can use a delegate. This can greatly simplify registering event handlers.
How do I do that?
To see how you can use an anonymous method, follow these steps:
Open a new Windows application in Visual Studio .NET 2005 and call it
AnonymousMethods
.Drag two controls onto the default form: a label and a button. Don’t bother renaming them.
Double-click the button. You will be taken to the code page, where you will enter the following code:
private void button1_Click(object sender,
EventArgs
e) { label1.Text = "Goodbye"; }Run and test the application. Clicking the button changes the label text to
Goodbye
.
Great. No problem. But there is a bit of overhead here. You must register the delegate (Visual Studio 2005 did this for you), and you must write an entire method to handle the button click. Anonymous methods help simplify these tasks.
To see how this works, click the Show All Files button, as shown in Figure 1-1.
Open Form1.Designer.cs and
navigate to the delegate for button1.Click
:
this.button1.Click += new System.EventHandler
(this.button1_Click);
You can’t replace this code without confusing the designer, but
we will eliminate this line by returning to the form and clicking the
lightning bolt in the Properties window, to go to the event handlers.
Remove the event handler for the Click
event.
If you return to Form1.Designer.cs you’ll find that the
button1.Click
event handler is not
registered!
Next, open Form1.cs and add
the following line to the constructor, after the call to InitializeComponent()
:
this.button1.Click += delegate { label1.Text = "Goodbye"; };
Now you are ready to delete (or comment out) the event handler method:
// private void button1_Click(object sender, EventArgs e) // { // label1.Text = "Goodbye"; // }
Run the application. It should work exactly as it did originally.
Instead of registering the delegate which then invokes the method, the code for the delegate is placed inline in an anonymous method: that is, an inline, unnamed block of code.
What about . . .
...using anonymous methods in my own code?
No problem. Not only can you use anonymous methods when you initialize delegates, but also you can pass a block of code anywhere you might otherwise use a delegate.
...what happens if I reference local variables in my anonymous block?
Good question. This can cause quite a bit of confusion and is a natural trap to fall into, especially if you don’t fully understand the consequences. C# allows local variables to be captured in the scope of the anonymous code block, and then they are accessed when the code block is executed. This can create some odd side effects, such as keeping objects around after they might otherwise have been collected.
...what about removing the handler for an event that I added with an anonymous delegate; how do I do that?
If you add an event handler with an anonymous delegate, you cannot remove it; therefore, I strongly recommend that you use anonymous delegates only for event handlers you expect to keep permanently attached.
You can use anonymous delegates for other
requirements, such as implementing a List.Find
method that takes, for example, a
delegate describing the search criteria.
Where can I learn more?
On the MSDN web site, you’ll find a good article touching on anonymous methods. Written by Juval Lowy, the article is titled “Create Elegant Code with Anonymous Methods, Iterators, and Partial Classes.” Also, visit O’Reilly’s ONDotnet.com site at http://www.ondotnet.com/pub/a/dotnet/2004/04/05/csharpwhidbeypt1.html.
Hide Designer Code with Partial Types
In previous versions of C# the entire definition for a class had to be in a single file. Now, using the partial
keyword, you can split your class across more than one
file. This provides two significant advantages:
You can have different team members working on different parts of the class.
Visual Studio 2005 can separate the designer-generated code from your own user code.
How do I do that?
The easiest way to see partial types at work is to examine the
previous example (AnonymousMethods
). Examine the declaration
of the class in Form1.cs:
partial class Form1 : Form
{
public Form1
( )
{
InitializeComponent( );
this.button1.Click += delegate { label1.Text = "Goodbye"; };
}
// private void button1_Click(object sender, EventArgs e)
// {
// label1.Text = "Goodbye";
// }
}
The partial
keyword indicates
that the code in this file does not necessarily represent the complete
definition of this class. In fact, you saw earlier that the Visual
Studio 2005 designer generated a second file, Form1.Designer.cs, which contains the rest
of the definition:
namespace AnonymousMethods
{
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose( );
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// Designer-generated initialization code
...
#endregion
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Button button1;
}
}
Together, these two files completely define the Form1
class, but you are spared dealing with
the designer-generated code unless you need to work with it. This
makes for simpler and cleaner development.
There is some “fine print” you need to be aware of in regard to using partial classes:
All partial type definitions must be modified with the
partial
keyword and must belong to the same namespace and the same module and assembly.The
partial
modifier can appear only before theclass
, interface
, andstruct
keywords.Access modifiers (
public
,private
, etc.) must match all the partial types of the same class.
What about . . .
...using partial classes in my own projects?
Microsoft suggests that partial classes can allow developers to work on different aspects of a class independently. It is still too early to see what best practices will emerge; I’m inclined to think that any class that is big enough to be divided in this way is big enough to be split into two (or more) classes. For now, the primary use of partial classes is to hide the cruft created by the designer.
Tip
Robert MacNeil reports in the PBS documentary “Do You Speak American?” that cruft is a neologism invented by surfers. However, the Online Computing Dictionary (http://www.instantweb.com/D/dictionary/index.html) reports that “This term is one of the oldest in the jargon and no one is sure of its etymology.” In any case, the Online Computing Dictionary defines cruft as “an unpleasant substance...excess; superfluous junk” and “the results of shoddy construction.”
Where can I learn more?
Developer.com provides a good article on partial types. Visit http://www.developer.com/net/net/article.php/2232061 for more information.
Create Static Classes
In addition to declaring methods as being static, now you also can declare classes as being static.
The purpose of a static class is to provide a set of static
utility methods scoped to the name of the class, much as you see done
with the Convert
class in the
Framework Class Library.
Note
In C# 2.0 you can declare an entire class as being static to signal that you’ve scoped a set of static utility methods to that class.
How do I do that?
To create a static class, just add the static
keyword before the class name and
make sure your static class meets the criteria described earlier for
static members. Also, note that static classes have the following
restrictions:
They can contain only static members.
It is not legal to instantiate a static class.
All static classes are sealed (you cannot derive them from a static class).
In addition to these restrictions, a static class cannot contain a constructor. Example 1-6 shows the proper use of a static class.
#region Using directives using System; #endregion namespace StaticClass { public static class CupConversions { public static int CupToOz(int cups) { return cups * 8; // 8 ounces in a cup } public static double CupToPint(double cups) { return cups * 0.5; // 1 cup = 1/2 pint } public static double CupToMil(double cups) { return cups * 237; // 237 mil to 1 cup } public static double CupToPeck(double cups) { return cups / 32; // 8 quarts = 1 peck } public static double CupToBushel(double cups) { return cups / 128; // 4 pecks = 1 bushel } } class Program { static void Main(string[ ] args) { Console.WriteLine("You might like to know that " + "1 cup liquid measure is equal to: "); Console.WriteLine(CupConversions.CupToOz(1) + " ounces"); Console.WriteLine(CupConversions.CupToPint(1) + " pints"); Console.WriteLine(CupConversions.CupToMil(1) + " milliliters"); Console.WriteLine(CupConversions.CupToPeck(1) + " pecks"); Console.WriteLine(CupConversions.CupToBushel(1) + " bushels"); } } }
Output:
You might like to know that 1 cup liquid measure is equal to: 8 ounces 0.5 pints 237 milliliters 0.03125 pecks 0.0078125 bushels
The Program
class’s main
method makes calls on the static methods of the CupConversions
class. Because CupConversions
exists only to provide
several helper methods, and no instance of CupConversions
is ever needed, it is safe
and clean to make CupConversions
a
static class.
What about . . .
...fields and properties? Can my static class have such members?
Yes, they can, but all the members (methods, fields, and properties) must be static.
Where can I learn more?
Eric Gunnerson has written an excellent article on static classes. You can find it in MSDN at http://blogs.msdn.com/ericgu/archive/2004/04/13/112274.aspx.
Express Null Values with Nullable Types
With new nullable types, you can assign value types a null value. This can be tremendously powerful, especially when working with databases where the value returned might be null; without nullable types you would have no way to express that an integer value is null, or that a Boolean is neither true nor false.
How do I do that?
You can declare a nullable type as follows:
System.Nullable<T> variable
Or, if you are within the scope of a generic type or method, you can write:
T? variable
Thus, you can create two Nullable
integer variables with these lines
of code:
System.Nullable<int> myNullableInt; int? myOtherNullableInt;
You can check whether a nullable variable is null in two ways as well. You can check like this:
if (myNullableInt.HasValue)
or like this:
if (myNullableInt != null)
Each will return true
if the
myNullableInt
variable is not null,
and false
if it is, as illustrated
in Example 1-7.
using System; namespace NullableTypes { public class Dog { private int age; public Dog(int age) { this.age = age; } } class Program { static void Main(string[ ] args) { int? myNullableInt = 25; double? myNullableDouble = 3.14159; bool? myNullableBool = null; // neither yes nor no // string? myNullableString = "Hello"; // not permitted // Dog? myNullableDog = new Dog(3); // not permitted if (myNullableInt.HasValue) { Console.WriteLine("myNullableInt is " + myNullableInt.Value); } else { Console.WriteLine("myNullableInt is undefined!"); } if (myNullableDouble != null) { Console.WriteLine("myNullableDouble: " + myNullableDouble); } else { Console.WriteLine("myNullableDouble is undefined!"); } if ( myNullableBool != null ) { Console.WriteLine("myNullableBool: " + myNullableBool); } else { Console.WriteLine("myNullableBool is undefined!"); } myNullableInt = null; // assign null to the integer // int a = myNullableInt; // won't compile int b; try { b = (int)myNullableInt; // will throw an exception if x is null Console.WriteLine("b: " + b); } catch (System.Exception e) { Console.WriteLine("Exception! " + e.Message); } int c = myNullableInt ?? -1; // will assign -1 if x is null Console.WriteLine("c: {0}", c); // careful about your assumptions here // If either type is null, all comparisons evaluate false! if (myNullableInt >= c) { Console.WriteLine("myNullableInt is greater than or equal to c"); } else { Console.WriteLine("Is myNullableInt less than c?"); } } } }
Output:
myNullableInt is 25 myNullableDouble: 3.14159 myNullableBool is undefined! Exception! Nullable object must have a value. c: -1 Is myNullableInt less than c?
What just happened?
Let’s focus on the Main
method. Five nullable types are created:
int? myNullableInt = 25; double? myNullableDouble = 3.14159; bool? myNullableBool = null; // neither yes nor no // string? myNullableString = "Hello";// Dog
? myNullableDog = newDog
(3);
The first three are perfectly valid, but you cannot create a nullable string or a nullable user-defined type (class), and thus they should be commented out.
We check whether each nullable type is null (or, equivalently,
whether the HasValue
property is
true
). If so, we print their value
(or equivalently, we access their Value
property).
After this the value null
is
assigned to myNullableInt
:
myNullableInt = null;
The next line would like to declare an integer and initialize it
with the value in myNullableInt
,
but this is not legal; there is no implicit conversion from a nullable
int
to a normal int
. You can solve this in two ways. The
first is with a cast:
b = (int)myNullableInt;
This will compile, but it will throw an exception at runtime if
myNullableInt
is null
(which is why we’ve enclosed it in a
try/catch block).
The second way to assign a nullable int
to an int
is to provide a default value to be used
in case the nullable int
is
null
:
int c = myNullableInt ?? -1;
This line reads as follows: “initialize int
c
with the value in myNullableInt
unless myNullableInt
is null
, in which case initialize c
to -1
.”
It turns out that all the comparison operators (>
, <
, <=
, etc.) return false
if either value is null
. Thus, a true value can be
trusted:
if (myNullableInt >= c)
{
Console
.WriteLine("myNullableInt is greater than or equal to c");
}
If the statement "myNullableInt
is greater than or equal to
c
" displays, you know that myNullableInt
is not null, nor is c
, and that myNullableInt
is greater than c
. However, a false value cannot be trusted
in the normal fashion:
else
{
Console
.WriteLine("Is myNullableInt less than c?");
}
This else
clause can be
reached if myNullableInt
is less
than c
, but it can also be reached
if either myNullableInt
or c
is null.
What about...
...Boolean null values? How are they compared to correspond to the SQL three-value Boolean type?
C# provides two new operators:
bool? operator &(bool? x, bool? y) bool? operator |(bool? x, bool? y)
You can use these operators to create the truth table depicted in Table 1-1.
Where can I learn more?
The Visual C# Developer Center has a good article on nullable types. Visit http://msdn.microsoft.com/vcsharp/2005/overview/language/nullabletypes/ for more information.
Access Objects in the Global Namespace
As in previous versions of C#, the namespace
keyword is used to declare a scope. This lets you organize
your code and prevents identifier collisions (for example, two different
classes with the same name), especially when using third-party
components.
Any object that is not defined within a specific namespace is in the global namespace. Objects in the global namespace are available to objects in any other namespace. If a name collision occurs, however, you will need a way to specify that you want the object in the global namespace rather than in the local namespace.
Note
The global namespace qualifier allows you to specify an identifier in the (default) global namespace rather than in the local namespace.
How do I do that?
To access objects in the global namespace, you use the new
global namespace qualifier (global:
:), as shown in Example 1-8.
using System; namespace GlobalNameSpace { class Program { // create a nested System class that will provide // a set of utilities for interacting with the // underlying system (conflicts with System namespace) public class System { } static void Main(string[ ] args) { // flag indicates if we're in a console app // conflicts with Console in System namespace bool Console = true; int x = 5; // Console.WriteLine(x); // won't compile - conflict with Console // System.Console.WriteLine(x); // conflicts with System global::System.Console.WriteLine(x); // works great. global::System.Console.WriteLine(Console); } } }
Output:
5 True
What just happened?
In this somewhat artificial example, you create a nested class
that you named System
and you
created a local Boolean variable named Console
. You have blocked access to the
global System
and Console
identifiers, so neither of these
lines will compile:
Console.WriteLine(x); System.Console.WriteLine(x);
To designate that you want to use the System
object in the global namespace, you will use the global
namespace qualifier:
global::System.Console
.WriteLine(x);
Notice that in the final line, the global namespace qualifier is
used to access the System
and
Console
objects in the global
namespace, and the unqualified Console
identifier is used to access the
local Boolean value:
global::System.Console
.WriteLine(Console);
What about . . .
...other uses for the double-colon (:
:)
operator?
The :
: operator is the
namespace alias qualifier. It always appears between two
identifiers:
identifierOne::identifierTwo
If identifierOne
is the
global namespace, this operator is used to find identifierTwo
within the global namespace.
But if identifierOne
is any
namespace other than the global namespace, the operator serves to
restrict the lookup of identifierOne
.
Where can I learn more?
The global namespace qualifier is mentioned in the MSDN article “Create Elegant Code with Anonymous Methods, Iterators, and Partial Classes” by Juval Lowy, available at http://msdn.microsoft.com/msdnmag/issues/04/05/c20/.
Limit Access Within Properties
It is now possible to restrict the accessibility level of the get
and set
accessors within a property using access modifiers. Usually you would
restrict access to the set
accessor
and make the get
accessor
public.
How do I do that?
Add the access modifier to either the get
or the set
accessor within the property, as
illustrated in Example
1-9.
#region Using directives
using System;
using System.Collections.Generic;
using System.Text;
#endregion
namespace LimitPropertyAccess
{
public class Employee
{
private string name;
public Employee(string name)
{
this.name = name;
}
public string Name
{
get { return name; }
protected
set { name = value; }
}
public virtual void ChangeName(string name)
{
// do work here to update many records
Name = name; // access the private accessor
}
}
class Program
{
static void Main(string[ ] args)
{
Employee joe = new Employee("Joe");
// other work here
string whatName = joe.Name; // works
// joe.Name = "Bob"; // doesn't compile
joe.ChangeName("Bob"); // works
Console.WriteLine("joe's name: {0}", joe.Name);
}
}
}
Output:
joe's name: Bob
What just happened?
The design of your Employee
class calls for the string name to be private. You anticipate that one
day you’ll want to move this to a database field, so you resolve that
all access to this field will be through a property, Name
.
Other classes are free to access Name
, but you do not want them to set the
name directly. If they are going to change the name field, they must
do so through the ChangeName
virtual method. You anticipate that derived classes will do different
work when an employee changes his name.
Thus you want to provide access to the set
accessor to this class’s methods and to
methods of any class that derives from this class, but not to other
classes. You accomplish this by adding the restricting access modifier
protected
to the set
accessor:
protected set { name = value; }
What about . . .
...restrictions on using access modifiers?
You cannot use these modifiers on interfaces or explicit
interface member implementations. You can use them only if both
get
and set
are included, and you can use them only
on one or the other.
Further, the modifier must restrict access, not broaden it.
Thus, you cannot make the property protected
and then use a modifier to make
get
public.
Where can I learn more?
You can learn more about properties and access modifiers by reading the MSDN article on properties at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/csref/html/vclrfPropertiesPG.asp.
Gain Flexibility with Delegate Covariance and Contravariance
Now it is legal to provide a delegate method with a return type
that is derived (directly or indirectly) from the delegate’s defined
return type; this is called covariance . That is, if a delegate is defined to return a Mammal
, it is legal to use that delegate to
encapsulate a method that returns a Dog
if Dog
derives from Mammal
and a Retriever
if Retriever
derives from Dog
which derives from Mammal
.
Note
Covariance allows you to encapsulate a method with a return type that is directly or indirectly derived from the delegate’s return type.
Similarly, now it is legal to provide a delegate method signature
in which one or more of the parameters is derived from the type defined
by the delegate. This is called contravariance. That is, if the delegate is
defined to take a method whose parameter is a Dog
you can use it to encapsulate a method
that passes in a Mammal
as a
parameter, if Dog
derives from
Mammal
.
How do I do that?
Covariance and contravariance give you more flexibility in the methods you encapsulate in delegates. The use of both covariance and contravariance is illustrated in Example 1-10.
Note
Contravariance allows you to encapsulate a method with a parameter that is of a type from which the declared parameter is directly or indirectly derived.
#region Using directives using System; using System.Collections.Generic; using System.Text; #endregion namespace CoAndContravariance { class Mammal { public virtual Mammal ReturnsMammal( ) { Console.WriteLine("Returning a mammal"); return this; } } class Dog : Mammal { public Dog ReturnsDog( ) { Console.WriteLine("Returning a dog"); return this; } } class Program { public delegate Mammal theCovariantDelegate( ); public delegate void theContravariantDelegate(Dog theDog); private static void MyMethodThatTakesAMammal(Mammal theMammal) { Console.WriteLine("in My Method That Takes A Mammal"); } private static void MyMethodThatTakesADog(Dog theDog) { Console.WriteLine("in My Method That Takes A Dog"); } static void Main(string[ ] args) { Mammal m = new Mammal( ); Dog d = new Dog( ); theCovariantDelegate myCovariantDelegate = new theCovariantDelegate(m.ReturnsMammal); myCovariantDelegate( ); myCovariantDelegate = new theCovariantDelegate(d.ReturnsDog); myCovariantDelegate( ); theContravariantDelegate myContravariantDelegate = new theContravariantDelegate(MyMethodThatTakesADog); myContravariantDelegate(d); myContravariantDelegate = new theContravariantDelegate(MyMethodThatTakesAMammal); myContravariantDelegate(d); } } }
Output:
Returning a mammal Returning a dog in My Method That Takes A Dog in My Method That Takes A Mammal
What just happened?
The Program
class in Example 1-10 declares two
delegates. The first is for a method that takes no parameters and
returns a Mammal
:
public delegate Mammal theCovariantMethod( );
In the run
method, you
declare an instance of Mammal
and
an instance of Dog
:
Mammal
m = newMammal
( );Dog
d = newDog
( );
You are ready to create your first instance of theCovariantDelegate
:
theCovariantDelegate myCovariantDelegate = new theCovariantDelegate(m.ReturnsMammal);
This matches the delegate signature (m.ReturnsMammal()
is a method that takes no
parameters and returns a Mammal
),
so you can invoke the method through the delegate:
myCovariantDelegate( );
Now you use covariance to encapsulate a second method within the same delegate:
myCovariantDelegate =
new theCovariantDelegate
(d.ReturnsDog);
This time, however, you are passing in a method (d.ReturnsDog( )
) that does not return a
Mammal
; it returns a Dog
that derives from Mammal
:
publicDog
ReturnsDog( ) {Console
.WriteLine("Returning a dog"); return this; }
That is covariance at work. To see contravariance, you declare a
second delegate to encapsulate a method that returns null
and takes a Dog
as a parameter:
public delegate voidtheContravariantDelegate
(Dog
theDog);
Your first instantiation of this delegate encapsulates a method
with the appropriate return value (void
) and parameter (Dog
):
myContravariantDelegate =
new theContravariantDelegate
(MyMethodThatTakesADog);
You can invoke the method through the delegate. Your second use
of this delegate, however, encapsulates a method that does not take a
Dog
as a parameter, but rather,
takes a Mammal
as a
parameter:
Note
Notice that with contravariance, you can pass in an object of the base type as a parameter where an object of the derived type is expected.
theContravariantDelegate
myContravariantDelegate = newtheContravariantDelegate
(MyMethodThatTakesAMammal);
MyMethodThatTakesAMammal
is
defined to take a Mammal
, not a
Dog
, as a parameter:
private void MyMethodThatTakesAMammal(Mammal
theMammal) {Console
.WriteLine("in My Method That Takes A Mammal"); }
Again, this works because a Dog
is a Mammal
and contravariance allows you to make
this substitution.
What about . . .
...contravariance? I get why with covariance I can return a
Dog
(a Dog
is a Mammal
), but why does contravariance work
the other way? Shouldn’t it accept a derived type when it expects a
base type?
Contravariance is consistent with Postel’s Law: “Be liberal in
what you accept, and conservative in what you send.” As the client,
you must make sure that what you send to the method will work with
that method, but as the implementer of the method you must be liberal
and accept the Dog
in any form,
even as a reference to its base class.
...what about reversing the usage of covariance and returning a base type where a derived type is expected? Can I do that?
No, it works in only one direction. You can, however, return a derived type where a base type is expected.
...what about reversing the usage of contravariance and passing in a derived type where a base type is expected?
You can’t do that, either. Again, it works in only one direction. You can, however, pass in a base type where a derived type is expected.
Where can I learn more?
Internet newsgroups contain numerous discussions about the advantages and disadvantages of object-oriented languages that support covariance and contravariance. For details about using covariance and contravariance in C# programs, see the MSDN Help file pages for the two topics.
Get Visual C# 2005: A Developer's Notebook now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.