BUY THIS BOOK
Add to Cart

Print Book $29.95


Add to Cart

Print+PDF $38.94

Add to Cart

PDF $23.99

Safari Books Online

What is this?

Add to UK Cart

Print Book £20.95

What is this?

Looking to Reprint or License this content?


Visual C# 2005: A Developer's Notebook
Visual C# 2005: A Developer's Notebook By Jesse Liberty
April 2005
Pages: 239

Cover | Table of Contents | Colophon


Table of Contents

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.
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 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];
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.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
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 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];
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.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
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.
From time to time you will decide to create your own generic collection classes.
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>
            
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.
Example 1-2. Creating your own generic collection
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);
      }
   }
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
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.
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.
You can constrain the datatypes your generic type accepts by using constraints.
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 class Node<T> : IComparable<Node<T>> where T:IComparable<T>
            
This line of code declares a class
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Enumerate Using Generic Iterators
In the previous examples you could not iterate over your list of 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( ));
}
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.
Adding iterators allows a client to iterate over your class using foreach.
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.
Example 1-4. Implementing IEnumerator, simplified
#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);
         }
      }
   }
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
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 class LinkedList<T> : IEnumerable<T>
public class Node<T> : IComparable<Node<T>>, IEnumerable<Node<T>>
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.)
Example 1-5. Enumerating through your linked list
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;
      }
   
   
      
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
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.
To see how you can use an anonymous method, follow these steps:
  1. Open a new Windows application in Visual Studio .NET 2005 and call it AnonymousMethods.
  2. Drag two controls onto the default form: a label and a button. Don't bother renaming them.
  3. 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";
    }
  4. Run and test the application. Clicking the button changes the label text to Goodbye.
Anonymous methods allow you to pass a block of code as a parameter.
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.
Figure 1-1: Show All Files button
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
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
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.
Using the partial keyword, you can split your class across more than one file.
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;
   }
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
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.
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.
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.
Example 1-6. Using static classes
#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");
      }
   }
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
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.
With nullable types, a value type such as bool or int can have the value null.
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.
Example 1-7. Nullable types
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?");
         }
   
      }
   }
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
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.
The global namespace qualifier allows you to specify an identifier in the (default) global namespace rather than in the local namespace.
To access objects in the global namespace, you use the new global namespace qualifier (global::), as shown in Example 1-8.
Example 1-8. Using the global namespace
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
In this somewhat artificial example, you create a nested class that you named System and you created a local Boolean variable named Console
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
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.
Now you can restrict the accessibility level of the get and set accessors within a property.
Add the access modifier to either the get or the set accessor within the property, as illustrated in Example 1-9.
Example 1-9. Limiting access to property accessors
#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
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
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
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.
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.
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.
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.
Example 1-10. Using covariance and contravariance
#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);
        }
    }
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 2: Visual Studio 2005
The Visual Studio 2005 IDE offers a number of new productivity features, including enhancements to the Visual Studio editor, pretested code snippets, enhanced IntelliSense, and significant help with creating and organizing your code. This chapter will explore the most useful and powerful of these changes.
Visual Studio 2005 is designed to enable extensive customization. For instance, you can control which tool windows are shown and how they are laid out, the placement and naming of menu commands, shortcut key combinations, help filters, and so forth.
You are encouraged to set up your development environment to match your own programming style. You can save these settings and bring them to other development machines, allowing you to have the same development environment settings on all the machines you work on. In addition, a team can share consistent settings, reducing confusion and simplifying code maintenance.
After you install Visual Studio 2005, the first time you open the program you are asked to choose a configuration. The configuration you choose is saved, along with any adjustments you make later to the IDE's look and feel, in a file named currentsettings.vssetting . Changes to your settings are saved for you automatically in that same file.
The default location for the settings file is [...] visual studio\settings\currentsettings.vssettings, but you can change that location by selecting Tools Options and then selecting Help/Import and Export Settings. There you will find a text box and a button that will open a disk browser.
Customize Visual Studio .NET 2005 to your own needs and bring those settings to other development machines.
You can also export your settings to a configuration file to bring to another computer by choosing Tools Import/Export Settings. The Import and Export Settings Wizard opens, asking if you want to export selected settings, import settings, or reset all your settings to one of the default collections, as shown in Figure 2-1.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Configure and Save Your Developer Environment
Visual Studio 2005 is designed to enable extensive customization. For instance, you can control which tool windows are shown and how they are laid out, the placement and naming of menu commands, shortcut key combinations, help filters, and so forth.
You are encouraged to set up your development environment to match your own programming style. You can save these settings and bring them to other development machines, allowing you to have the same development environment settings on all the machines you work on. In addition, a team can share consistent settings, reducing confusion and simplifying code maintenance.
After you install Visual Studio 2005, the first time you open the program you are asked to choose a configuration. The configuration you choose is saved, along with any adjustments you make later to the IDE's look and feel, in a file named currentsettings.vssetting . Changes to your settings are saved for you automatically in that same file.
The default location for the settings file is [...] visual studio\settings\currentsettings.vssettings, but you can change that location by selecting Tools Options and then selecting Help/Import and Export Settings. There you will find a text box and a button that will open a disk browser.
Customize Visual Studio .NET 2005 to your own needs and bring those settings to other development machines.
You can also export your settings to a configuration file to bring to another computer by choosing Tools Import/Export Settings. The Import and Export Settings Wizard opens, asking if you want to export selected settings, import settings, or reset all your settings to one of the default collections, as shown in Figure 2-1.
Figure 2-1: The Import and Export Settings Wizard
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Configure Your Application
A number of properties affect how your program will be compiled and run. These settings are important, and when you need to change them you want to be able to find them easily. Now you can set all your application properties in one place.
Create a new project and name it 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.
Figure 2-6: Project properties
As you click through the tabs you'll see you have immediate access to all the properties that affect how your project is built, debugged, and distributed. The final tab, Publish, can greatly simplify the work required in setting up your program for distribution, as shown in Figure 2-7.
Figure 2-7: The Publish tab
When you clicked the Publish tab you were offered the opportunity to publish your application (once built) to a specific URL (for distribution over the Web) as well as the ability to set the version number. The tab also includes a button to open the Publish Wizard, which walks you through the necessary setup steps to publish your fully developed application.
...publishing via an FTP server or using settings other than the default settings shown in the properties dialog?
To do this, click the Publish Wizard button shown at the bottom of Figure 2-7. Here's where you can easily modify these settings, guided by the wizard to ensure that you get the syntax right, as shown in Figure 2-8.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Make the Editor Work for You
The editor can be your most important tool for creating programs, yet few developers take the time to fully master it; and thus they miss out on potential gains in their productivity.
The Visual Studio editor can be your most important tool for creating programs.
In this lab, you'll see how Visual Studio 2005 and several new features can work for you.
The best way to explore the editor is to open a new Windows project. Let's call it 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.
Drag a label and a button anywhere onto the form, and then double-click the button to create an event handler. You should find that Visual Studio 2005 has opened a code-behind file named Form1.cs and has placed you inside the designer-generated button1_Click method.

Section 2.3.1.1: Change your code

Make some minor changes to the code in your editor. Notice the yellow strip down the side of the window. This indicates changes to your code that have not yet been saved. When the changes are saved, this strip turns green.
A yellow strip indicates unsaved changes. A green strip indicates changes that have been saved.

Section 2.3.1.2: Use IntelliSense to complete your statement

Inside the 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.
By default, IntelliSense leaps to the member you are most likely to want to use (often determined by remembering the member you used most recently). You can turn this off in Tools
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Use Refactoring to Speed Revision of Your Code
For most of us mortal programmers, the first version of complex code we create is not always the best it can be. As we code, we often decide to change the names of variables, factor out common routines into methods, and encapsulate member variables into properties. Some of these tasks can lead to difficulties: for example, changing the name of a variable can create subtle bugs if you forget to update references to the variable throughout the code.
Visual Studio 2005 offers a number of refactoring features that automate this process and ensure that the code is left in a consistent state.
To learn more about the new refactoring features in Visual Studio 2005, first create a new Console application and name it SimplifyCoding. Replace the code provided by Visual Studio 2005 with the code shown in Example 2-1.
Example 2-1. Starting code for SimplifyCoding example
#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);
      }
   
   }
}
Before you run this program in the debugger, be sure to go to Tools Options, click Debugging/General, and check the box marked "Redirect all console output to the Quick Console Window." This will let you see the results of your Console application within the debugger.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Use Code Snippets to Save Typing
There are a number of patterns that you use all the time (for example, for statements).Visual Studio 2005 makes it easy to insert these in your code.
For example, returning to the previous lab, say you want to add some conditional code to actually test if the dog is overweight. Begin by highlighting the code within the TestDogOverweight method, and then right-click it. Choose Surround With: and the Surround With menu pops up, as shown in Figure 2-17.
Figure 2-17: The Surround With menu with the if statement highlighted
Choose the 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.
Figure 2-18: Filling in the if statement
With a couple of keystrokes, you were able to surround a block of code with a well-formed 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.
...using expansions without surrounding existing code?
Rather than using expansion surround, just use the code expansion snippet. The expansions in IntelliSense allow you to quickly create any of a wide variety of coding structures. For example, if you just type the word 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.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Examine Objects While Debugging Them
In Visual Studio 2005 you can look inside complex objects, including user-defined classes and collections, right from within the editor. You do so by hovering your mouse cursor over the object while in debug mode. This is the same as in previous versions of Visual Studio, but the amount of information provided is much greater than before.
Now you can peer inside complex objects. It's MRI for programmers.
To see an example of how to look at the state of complex objects, place a breakpoint on the last line of the 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.
Figure 2-19: Looking inside objects
...seeing other types of data such as XML or datasets?
The debugger comes with built-in visualizers for text, HTML, and XML. You are also free to create your own. The next lab examines how to use the XML Visualizer.
A number of good books on using debuggers are available, including the seminal work Code Complete by Steve McConnell (Microsoft Press).
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Visualize XML Data
As noted in the previous lab, the debugger allows you to examine text, HMTL, and XML. In this lab you'll create an XML document, read it into a program, and then examine the XML data using the XML Visualizer.
Visual Studio 2005 provides an XML Visualizer for examining XML data.
Create a new Console application named 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.
Example 2-2. Valid XML data sample
<?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>
Next, write a 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.
Example 2-3. Reading the XML file
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);
         }
   
      }
   }
}
Place a breakpoint on the last line of the Main method. When you hit the breakpoint, hover your mouse cursor over the complete XML string. The strin