Cover | Table of Contents | Colophon
System.Object, and
because everything derives from
System.Object, these collections could hold any
type at all; that is, they were not type-safe.Employee objects in C# 1.x. To do so, you would
use an
ArrayList
,
which holds objects of the System.Object type.
Adding new Employees to the collection was not a
problem because Employees 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];
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.System.Object, and
because everything derives from
System.Object, these collections could hold any
type at all; that is, they were not type-safe.Employee objects in C# 1.x. To do so, you would
use an
ArrayList
,
which holds objects of the System.Object type.
Adding new Employees to the collection was not a
problem because Employees 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];
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.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.int) with the
generic type (for example, T).private int data;
private T data; // T is a generic Type Parameter
T) is
defined by you when you create your collection class by placing the
type parameter inside angle brackets (< >):public class Node<T>
T for
"type," but Microsoft recommends
you use longer, more descriptive type names (for example,
Node<DocumentType>).T," which at runtime will become
"Node of int" or
node of any other type the compiler recognizes.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);
}
}
}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.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.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>
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)
CompareTo method.IComparable. You accomplish this with a
constraint using the keyword where:public class Node<T> : IComparable<Node<T>> where T:IComparable<T>
Pilgrims 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( )); }
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'
GetEnumerator was somewhat complicated and always
tedious, but in C# 2.0 it is greatly simplified.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);
}
}
}
}LinkedList
class, you'll implement
IEnumerable<T> on both
LinkedList and the Node class:public class LinkedList<T> : IEnumerable<T> public class Node<T> : IComparable<Node<T>>, IEnumerable<Node<T>>
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;
}
AnonymousMethods.private void button1_Click(object sender, EventArgs e)
{
label1.Text = "Goodbye";
}
Goodbye.
button1.Click:this.button1.Click += new System.EventHandler(this.button1_Click);
Click event.partial
keyword, you can split your class
across more than one file. This provides two significant advantages: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";
// }
}
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;
}
}Convert class in the Framework Class Library.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:#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");
}
}
}System.Nullable<T> variable
T? variable
Nullable integer
variables with these lines of code:System.Nullable<int> myNullableInt; int? myOtherNullableInt;
if (myNullableInt.HasValue)
if (myNullableInt != null)
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?");
}
}
}
}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.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);
}
}
}
5 True
System and you created a local Boolean
variable named Consoleget and set
accessors within a property using access modifiers. Usually you would
restrict access to the set accessor and make the
get accessor public.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);
}
}
}
joe's name: Bob
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.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
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.Dog you can use it to encapsulate a method
that passes in a Mammal as a parameter, if
Dog derives from Mammal.#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);
}
}
}
SimplifyCoding
(you'll use this project in the next lab as well).
Right-click the project and click Properties. A properties window opens with
numerous tabs on the lefthand side, as shown in Figure 2-6.
EditorExploration.
Visual Studio starts you out with a nice clean form named
Form1, and also creates three additional files:
Form1.cs to hold the code related to the form,
Form1.designer.cs to hold the code created by
the form designer, and Program.cs to hold the
code related to the application.button1_Click method.button1_click method, type the letter
l (lowercase L). IntelliSense
leaps to the label1 member, which is just what
you want. Insert a period (.) and IntelliSense
will fill in the name label1 and open up all the
public methods, properties, and so forth for that control.SimplifyCoding. Replace the code provided by
Visual Studio 2005 with the code shown in Example 2-1.#region Using directives
using System;
using System.Collections.Generic;
using System.Text;
#endregion
namespace SimplifyCoding
{
public class Dog
{
public int age;
public int weight;
public Dog(int age, int weight)
{
this.age = age;
this.weight = weight;
}
public void Method1( )
{
Console.WriteLine("This dog is overweight");
}
public void Method2(int x)
{
weight += x;
Console.WriteLine("This dog is overweight");
}
public override string ToString( )
{
return "I weigh " + weight + " and I'm " + age + " years old";
}
}
class Program
{
static void Main(string[ ] args)
{
Dog d = new Dog(5, 50);
Console.WriteLine(d);
d.weight = 70;
Console.WriteLine(d);
d.Method1( );
d.Method2(35);
}
}
}
for statements).Visual Studio 2005 makes
it easy to insert these in your code.TestDogOverweight
method, and then right-click it.
Choose Surround With: and
the
Surround With menu pops up, as shown
in Figure 2-17.
if statement (be careful not to choose
#if), and an if block is
created around your block of code. The if
condition is initialized true and is highlighted
for you to type in new code. IntelliSense helps you complete the
code, as shown in Figure 2-18.
if statement. The
variables used in the if statement were offered by
IntelliSense, enabling you to make sure you used the correct spelling
and capitalization.foreach followed by a tab, a
foreach loop is created with each editable field
highlighted in yellow. You can tab through the fields with the Tab
key, and when you are done, pressing Enter or Esc will return the
editor to normal.SimplifyCoding program shown in Example 2-1. Run the debugger to the breakpoint and place
your cursor over the variable d, which represents
an instance of Dog. Notice that the text that
would be rendered by calling ToString on the
Dog object is shown, just as in previous versions.
Now, however, a plus sign (+) appears next to the
variable. Hover your mouse cursor over the plus sign; the plus sign
turns to a minus sign, and you'll see the internals
of the instance, as shown in Figure 2-19.
ExamineXML.
Add an XML file to the project by right-clicking the project and
choosing Add → New Item
→ XML File. Name the new
XML file BookList.xml and populate it with the
valid XML data shown in Example 2-2.<?xml version="1.0" ?>
<Books>
<book>
<BookName>Programming C#</BookName>
<Author>Jesse Liberty</Author>
<Publisher>O'Reilly Media</Publisher>
</book>
<book>
<BookName>Programming ASP.NET</BookName>
<Author>Jesse Liberty</Author>
<Author>Dan Hurwitz</Author>
<Publisher>O'Reilly Media</Publisher>
</book>
<book>
<BookName>Visual C# Notebook</BookName>
<Author>Jesse Liberty</Author>
<Publisher>O'Reilly Media</Publisher>
</book>
</Books>
Test method to read the XML file
using a StreamReader object and concatenate all
the strings read into a single long XML string, as shown in Example 2-3.using System;
using System.IO;
using System.Xml;
namespace ExamineXML
{
class Program
{
static void Main(string[ ] args)
{
using (StreamReader reader = File.OpenText(@"..\..\BookList.xml"))
{
string completeXML = reader.ReadToEnd( );
Console.WriteLine("Received: {0}", completeXML);
}
}
}
}
Main
method. When you hit the breakpoint, hover your mouse cursor over the
complete XML string. The strin