BUY THIS BOOK

Safari Books Online

What is this?

Looking to Reprint this content?


C# in a Nutshell
C# in a Nutshell

By Peter Drayton, Ben Albahari, Ted Neward

Cover | Table of Contents | Colophon


Table of Contents

Chapter 1: Introducing C# and the .NET Framework
C# is a new programming language from Microsoft designed specifically to target the .NET Framework. Microsoft's .NET Framework is a runtime environment and class library that dramatically simplifies the development and deployment of modern, component-based applications.
When the .NET Framework and C# language compiler were shipped in final form in January 2002, both platform and programming language had already garnered much industry attention and widespread use among Microsoft-centric early adopters. Why this level of success? Certainly, the C# language and the .NET Framework address many of the technical challenges facing modern developers as they strive to develop increasingly complex distributed systems with ever-shrinking schedules and team sizes.
However, in addiction to its technical merits, one of the main reasons for the success that the language and platform has enjoyed thus far is the unprecedented degree of openness that Microsoft has shown. From July 2000 to January 2002, the .NET Framework underwent an extensive public beta that allowed tens of thousands of developers to "kick the tires" of the programming environment. This allowed Microsoft to both solicit and react to developer community feedback before finalizing the new platform.
Additionally, the key specifications for both the language and the platform have been published, reviewed, and ratified by an international standards organization called the European Computer Manufacturers Association (ECMA). These standardization efforts have led to multiple third-party initiatives that bring the C# language and the .NET platform to non-Microsoft environments. They have also prompted renewed interest among academics in the use of Microsoft technologies as teaching and research vehicles.
Lastly, although the language and platform are shiny and new, the foundations for the C# language and the .NET Framework have been years in the making, reaching back more than half a decade. Understanding where the language and platform have come from gives us a better understanding of where they are headed.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
The C# Language
Reports of a new language from Microsoft first started surfacing in 1998. At that time the language was called COOL, and was said to be very similar to Java. Although Microsoft consistently denied the reports of the new language, rumors persisted.
In June 2000, Microsoft ended the speculation by releasing the specifications for a new language called C# (pronounced "see-sharp"). This was rapidly followed by the release of a preview version of the .NET Framework SDK (which included a C# compiler) at the July 2000 Professional Developer's Conference (PDC) in Orlando, Florida.
The new language was designed by Anders Hejlsberg (creator of Turbo Pascal and architect of Delphi), Scott Wiltamuth, and Peter Golde. Described in the C# Language Specification as a "...simple, modern, object-oriented, and type-safe programming language derived from C and C++," C# bears many syntactic similarities to C++ and Java.
However, focusing on the syntactic similarities between C# and Java does the C# language a disservice. Semantically, C# pushes the language-design envelope substantially beyond where the Java language was circa 2001, and could rightfully be viewed as the next step in the evolution of component-oriented programming languages. While it is outside the scope of this book to perform a detailed comparison between C# and Java, we urge interested readers to read the widely cited article "A Comparative Overview of C# and Java," by co-author Ben Albahari, available at http://genamics.com/developer/csharp_comparative.htm.
Over the last 10 years, programming techniques such as object-oriented design, interface-based programming, and component-based software have become ubiquitous. However, programming language support for these constructs has always lagged behind the current state-of-the-art best practices. As a result, developers tend to either depend on programming conventions and custom code rather than direct compiler and runtime support, or to not take advantage of the techniques at all.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
The .NET Framework
The Microsoft .NET Framework consists of two elements: a runtime environment called the Common Language Runtime (CLR), and a class library called the Framework Class Library (FCL). The FCL is built on top of the CLR and provides services needed by modern applications.
While applications targeting the .NET Framework interact directly with the FCL, the CLR serves as the underlying engine. In order to understand the .NET Framework, one first must understand the role of the CLR.
The CLR is a modern runtime environment that manages the execution of user code, providing services such as JIT compilation, memory management, exception management, debugging and profiling support, and integrated security and permission management.
Essentially, the CLR represents the foundation of Microsoft's computing platform for the next decade. However, it has been a long time in the making. Its origins can be traced back to early 1997, when products such as Microsoft Transaction Server (MTS) were starting to deliver on the promise of a more declarative, service-oriented programming model. This new model allowed developers to declaratively annotate their components at development time, and then rely on the services of a runtime (such as MTS) to hijack component activation and intercept method calls, transparently layering in additional services such as transactions, security, just-in-time (JIT) activation, and more. This need to augment COM type information pushed the limits of what was possible and useful with IDL and type libraries. The COM+ team set out to find a generalized solution to this problem.
The first public discussion of a candidate solution occurred at the 1997 PDC in San Diego, when Mary Kirtland and other members of the COM+ team discussed a future version of COM centered on something called the COM+ Runtime, and providing many of the services such as extensible type information, cross-language integration, implementation inheritance and automatic memory management that ultimately resurfaced in the CLR.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
ECMA Standardization
One of the most encouraging aspects about the .NET Framework is the degree of openness that Microsoft has shown during its development. From the earliest public previews, core specifications detailing the C# language, the classes in the FCL, and the inner workings of the CLR have been freely available.
However, this openness was taken to a new level in November 2000 when Microsoft, along with co-sponsors Intel and HP, officially submitted the specifications for the C# language, a subset of the FCL, and the runtime environment to ECMA for standardization.
This action began an intense standardization process. Organizations participating in the effort included Microsoft, HP, Intel, IBM, Fujitsu, Sun, Netscape, Plum Hall, OpenWave, and others. The work was performed under the auspices of ECMA technical committee TC39, the same committee that had previously standardized the JavaScript language as ECMAScript.
TC39 chartered two new task groups to perform the actual standardization work: one to focus on the C# language, the other to focus on what became known as the Common Language Infrastructure (CLI).
The CLI consisted of the runtime engine and the subset of the FCL being standardized. Conceptually, Microsoft's CLR is intended to be a conforming commercial implementation of the runtime engine specified in the CLI, and Microsoft's FCL is intended to be a conforming commercial implementation of the class library specified in the CLI (although obviously, it is a massive superset of the 294 classes ultimately specified in the CLI).
After more than a year of intense effort, the task groups completed their standardization work and presented the specifications to the ECMA General Assembly. On December 13, 2001, the General Assembly ratified the C# and CLI specifications as international standards, assigning them the ECMA standards numbers of ECMA-334 (C#) and ECMA-335 (the CLI). Copies of the ECMA standards are available at http://www.ecma.ch.
Critics have claimed that the ECMA standardization process was merely a ploy by Microsoft to deflect Java's cross-platform advantages. However, the qualifications and seniority of the people working on the standardization effort, and their level of involvement during the lengthy standardization cycle, tell a different story. Microsoft, along with its co-sponsors and the other members of the standardization task groups, committed some of its best and brightest minds to this effort, spending a huge amount of time and attention on the standardization process. Given that this effort occurred concurrently with the development and release of the .NET Framework itself, this level of investment by Microsoft and others flies in the face of the conspiracy theories.
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: C# Language Basics
In this chapter, we explain a simple C# program and introduce the basics of the C# language.
Here is a simple C# program:
namespace FirstProgram {
  using System;
  class Example {
    static void Main () {
      Console.WriteLine ("Hello world!");
    }
  }
}
A C# program is composed of types (typically classes) that we organize into namespaces. Each type contains function members (typically methods and properties), as well as data members (typically fields). Methods contain a series of statements that are executed sequentially. In our program, we define a class named Example that contains a method named Main, which has a single statement that writes Hello world! to the console window. C# recognizes this method as the default entry point of execution, so that's where the program begins.
The Console class encapsulates standard input/output functionality, providing methods such as WriteLine. To use types from another namespace, use the using directive. Since the Console class resides in the System namespace, we go via System; similarly, types from other namespaces could use our Example class by using FirstProgram.
In C#, there are no standalone functions; they are always associated with a type, or, as we will see, instances of that type. Our program is simple, and makes use of only static members, which means the member is associated with its type, rather than instances of its type. In addition, we make use of only void methods, which means these methods do not return a value.
Throughout this book, most of the examples contain this stub code:
using System;
class Test {
  static void Main () {
    ...
  }
}
Identifiers are names programmers choose for their types, methods, variables, etc. An identifier must be a whole word, essentially made up of Unicode characters starting with a letter or underscore, and may not clash with a keyword. As a special case, 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!
A First C# Program
Here is a simple C# program:
namespace FirstProgram {
  using System;
  class Example {
    static void Main () {
      Console.WriteLine ("Hello world!");
    }
  }
}
A C# program is composed of types (typically classes) that we organize into namespaces. Each type contains function members (typically methods and properties), as well as data members (typically fields). Methods contain a series of statements that are executed sequentially. In our program, we define a class named Example that contains a method named Main, which has a single statement that writes Hello world! to the console window. C# recognizes this method as the default entry point of execution, so that's where the program begins.
The Console class encapsulates standard input/output functionality, providing methods such as WriteLine. To use types from another namespace, use the using directive. Since the Console class resides in the System namespace, we go via System; similarly, types from other namespaces could use our Example class by using FirstProgram.
In C#, there are no standalone functions; they are always associated with a type, or, as we will see, instances of that type. Our program is simple, and makes use of only static members, which means the member is associated with its type, rather than instances of its type. In addition, we make use of only void methods, which means these methods do not return a value.
Throughout this book, most of the examples contain this stub code:
using System;
class Test {
  static void Main () {
    ...
  }
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Identifiers and Keywords
Identifiers are names programmers choose for their types, methods, variables, etc. An identifier must be a whole word, essentially made up of Unicode characters starting with a letter or underscore, and may not clash with a keyword. As a special case, the @ prefix may be used to avoid a clash with a keyword, but is not considered part of the identifier. For instance, the following two identifiers are equivalent:
Korn
@Korn
C# identifiers are case-sensitive, though for compatibility with other languages, you should not differentiate public or protected identifiers by case alone.
Here is a list of C# keywords:
abstract
as
base
bool
break
byte
case
catch
char
checked
class
const
continue
decimal
default
delegate
do
double
else
enum
event
explicit
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Type Basics
A C# program is best understood in terms of three basic elements: functions, data, and types. This book assumes you have some programming experience, so let's start with a brief overview of functions and data (which you should have some familiarity with) and then move on to explain types in more detail.
Functions
A function performs an action by executing a series of statements. For example, you may have a function that returns the distance between two points, or a function that calculates the average of an array of values. A function is a way of manipulating data.
Data
Data is values that functions operate on. For example, you may have data holding the coordinates of a point, or data holding an array of values. Data always has a particular type.
Types
A type has a set of data members and function members. The function members are used to manipulate the data members. The most common types are classes and structs, which provide a template for creating data; data is always an instance of a type.
Types are quite an abstract concept, so let's look at two concrete examples.
The string class specifies a sequence of characters. This means you can store values such as ".NET" or "http://oreilly.com". You can also perform functions such as returning the character at a particular position on the string or getting its lowercase representation.
In this example, we output the lower case representation of ".NET" (which will be ".net"), and return the length of the string (which will be 4). To do this, we first create an instance of a string, then use the ToLower method and Length property, which are function members of that string.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Value Types and Reference Types
All C# types fall into the following categories:
  • Value types (struct, enum)
  • Reference types (class, array, delegate, interface)
  • Pointer types
The fundamental difference between the three main categories (value types, reference types, and pointer types) is how they are handled in memory. The following sections explain the essential differences between value types and reference types. Pointer types fall outside mainstream C# usage, and are covered later in Chapter 4.
Value types are the easiest types to understand. They directly contain data, such as the int type (holds an integer), or the bool type (holds a true or false value). A value type's key characteristic is when you assign one value to another, you make a copy of that value. For example:
using System;
class Test {
  static void Main () {
    int x = 3;
    int y = x; // assign x to y, y is now a copy of x
    x++; // increment x to 4
    Console.WriteLine (y); // prints 3
  }
}
Reference types are a little more complex. A reference type really defines two separate entities: an object, and a reference to that object. This example follows exactly the same pattern as our previous example, but notice how the variable y is updated, while in our previous example, y remained unchanged:
using System;
using System.Text;
class Test {
  static void Main () {
    StringBuilder x = new StringBuilder ("hello");
    StringBuilder y = x;
    x.Append (" there");
    Console.WriteLine (y); // prints "hello there"
  }
}
This is because the StringBuilder type is a reference type, while the int type is a value type. When we declared the StringBuilder variable, we were actually doing two separate things, which can be separated into these two lines:
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Predefined Types
This section explains each of C#'s predefined types:
  • Value types
    • Integer, signed (sbyte, short, int, long)
    • Integer, unsigned (byte, ushort, uint, ulong)
    • Floating-point (float, decimal, char, bool)
  • Reference types
    • Object
    • String
All of these types alias types found in the System namespace. For example, there is only a syntactic difference between these two statements:
int i = 5;
System.Int32 i = 5;
This table lists the integral types and their features:
C# type
System type
Size
Signed
sbyte
System.SByte
1 byte
yes
short
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Arrays
type [*]
+ array-name = 
[
  new type [ dimension+ ][*]*; |
  { value1, value2, ... };
]
Note that [*] is the set: [] [,] [, ,] ...
Arrays allow a group of elements of a particular type to be stored in a contiguous block of memory. An array is specified by placing square brackets after the element type. For example:
char[] vowels = new char[] {`a','e','i','o','u'};
Console.WriteLine(vowels [1]); // Prints "e"
This prints "e" because array indexes start at 0. To support other languages, .NET can create arrays based on arbitrary start indexes, but all the libraries use zero-based indexing. Once an array has been created, its length cannot be changed. However, the System.Collection classes provide dynamically sized arrays, as well as other data structures, such as associative (key/value) arrays.
Multidimensional arrays come in two varieties: rectangular and jagged. Rectangular arrays represent an n-dimensional block, while jagged arrays are arrays of arrays. In this example we make use of the for loop, which is explained in the statements section. The for loops here simply iterate through each item in the arrays.
// rectangular
int [,,] matrixR = new int [3, 4, 5]; // creates 1 big cube
// jagged
int [][][] matrixJ = new int [3][][];
for (int i = 0; i < 3; i++) {
   matrixJ[i] = new int [4][];
   for (int j = 0; j < 4; j++)
      matrixJ[i][j] = new int [5];
} 
// assign an element
matrixR [1,1,1] = matrixJ [1][1][1] = 7;
For convenience, local and field declarations may omit the array type when assigning a known value, since the type is specified in the declaration anyway:
int[,] array = {{1,2},{3,4}};
Arrays know their own length. For multidimensional array methods, the array's GetLength method returns the number of elements for a given dimension, which is from 0 (the outermost) to the array's
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Variables and Parameters
A variable represents a typed storage location. A variable can be a local variable, parameter, array element, an instance field, or a static field.
All variables have an associated type, which essentially defines the possible values the variable can have and the operations that can be performed on that variable. C# is strongly typed, which means the set of operations that can be performed on a type are enforced at compile time, rather than at runtime. In addition, C# is type-safe, which ensures that a variable can be operated on through only via the correct type with the help of runtime checking (except in unsafe blocks; see Section 4.8 in Chapter 4).
All variables in C# (except in unsafe contexts) must be assigned a value before they are used. A variable is either explicitly assigned a value or automatically assigned a default value. Automatic assignment occurs for static fields, class instance fields, and array elements not explicitly assigned a value. For example:
using System;
class Test {
  int v;
  // Constructors that initalize an instance of a Test
  public Test() {} // v will be automatically assigned to 0
  public Test(int a) { // explicitly assign v a value
     v = a;
  }
  static void Main() {
    Test[] tests = new Test [2]; // declare array
    Console.WriteLine(tests[1]); // ok, elements assigned to null
    Test t;
    Console.WriteLine(t); // error, t not assigned before use
  }
}
Essentially the default value for all primitive (or atomic) types is zero:
Type
Default value
Numeric types
0
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Expressions and Operators
An expression is a sequence of operators and operands that specifies a computation. C# has unary operators, binary operators, and one ternary operator. Complex expressions can be built because an operand may itself be an expression, such as the operand (1 + 2) shown in the following example:
((1 + 2) / 3)
When an expression contains multiple operators, the precedence of the operators controls the order in which the individual operators are evaluated. When the operators are of the same precedence, their associativity determines the order. Binary operators (except for assignment operators) are left-associative; i.e., they are evaluated from left to right. The assignment operators, unary operators, and the conditional operator are right-associative; i.e., they are evaluated from right to left.
For example:
1 + 2 + 3 * 4
is evaluated as:
((1 + 2) + (3 * 4))
because * has a higher precedence that +, and + is a binary operator that is left-associative. You can insert parentheses to change the default order of evaluation. C# overloads operators, which means the same operator, or has different meanings for different types.
The following table lists C#'s operators in order of precedence. Operators in the same box have the same precedence, and operators in italic may be overloaded for custom types.
Table 2-2: Operator Precedence Table
Category
Operators
Primary
Grouping: (x)
Member access: x.y
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Statements
Execution in a C# program is specified by a series of statements that execute sequentially in the textual order in which they appear. All statements in a procedural-based language such as C# are executed for their effect. For instance, a statement may assign an expression to a variable, repeatedly execute a list of statements, or jump to another statement.
So that multiple statements can be grouped together, zero or more statements may be enclosed in braces to form a statement block.
[variable =]? expr;
An expression statement evaluates an expression, either assigning its result to a variable or generating side effects (i.e., invocation, new, ++, --). An expression statement ends in a semicolon. For example:
x = 5 + 6; // assign result
x++; // side effect
y = Math.Min(x, 20); // side effect and assign result
Math.Min(x, y); // discards result, but ok, there is a side effect
x == y; // error, has no side effect, and does not assign result
The variable declaration syntax is:
type [variable [ = expr ]?]+ ;
The constant declaration syntax is:
const type [variable = constant-expr]+;
A declaration statement declares a new variable, optionally assigning the result of an expression to that variable. A declaration statement ends in a semicolon.
The scope of a local or constant variable extends to the end of the current block. You cannot declare another local variable with the same name in the current block or in any nested blocks. For example:
bool a = true;
while(a) {
  int x = 5;
  if (x==5) {
    int y = 7;
    int x = 2; // error, x already defined
 }
 Console.WriteLine(y); // error, y is out of scope
}
A constant declaration is like a variable declaration, except that the variable cannot be changed after it has been declared:
const double speedOfLight = 2.99792458E08;
speedOfLight+=10; // error
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Namespaces
These are defined in files, organized by namespaces, compiled into a module, then grouped into an assembly.
These organizational units are crosscutting. For example, typically a group of namespaces belong to one assembly, but a single namespace may in fact be spread over multiple assemblies (see Chapter 12).
File organization is almost of no significance to the C# compiler — a whole project could be merged into one .cs file and it would still compile (preprocessor statements are the only exception to this). However, it's generally tidy to have one type in one file, with the filename matching the name of the class and the directory the file is in matching the name of the class's namespace.
namespace name+           // Dot-delimited
{
 using-statement*
 [namespace-declaration | 
  type-declaration]*      // No delimiters
}
A namespace lets you group related types into a hierarchical categorization. Generally the first name is the name of your organization, and it gets more specific from there:
namespace MyCompany.MyProduct.Drawing {
  class Point {int x, y, z;}
  delegate void PointInvoker(Point p);
}

Section 2.10.2.1: Nesting namespaces

You may also nest namespaces instead of using dots. This example is semantically identical to the previous example:
namespace MyCompany {
  namespace MyProduct {
    namespace Drawing {
      class Point {int x, y, z;}
      delegate void PointInvoker(Point p);
    }
  }
}

Section 2.10.2.2: Using a type with its fully qualified name

To use the Point from another namespace, you may refer to it with its fully qualified name. The namespace a type is within actually becomes part of the type name:
namespace TestProject {
  class Test {
    static void Main() {
      MyCompany.MyProduct.Drawing.Point x;
    }
  }
}
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 3: Creating Types in C#
In this chapter, we cover creation of types in C#, including classes, inheritance, access modifiers, structs, interfaces, and enums.
               attributes? unsafe? access-modifier?
new? 
[ abstract | sealed ]?
class class-name 
[: base-class | 
 : interface+ | 
 : base-class, interface+ ]?
{ class-members }
In C#, a program is built by defining new types, each with a set of data members and function members. Custom types should form higher-level building blocks that are easy to use, and closely model your problem space.
In this example, we simulate an astronaut jumping on different planets, using three classes: Planet, Astronaut, and Test to test our simulation.
First, let's define the Planet class. By convention, we define the data members of the class at the top of the class declaration. There are two data members here: the name and gravity fields, which store the name and gravity of a planet. We then define a constructor for the planet. Constructors are function members that allow you to initialize an instance of your class. We initialize the data members with values fed to the parameters of the constructor. Finally, we define two more function members, which are properties that allow us to get the "Name" and "Gravity" of a planet. The Planet class looks like this:
using System;
  
class Planet {
  string name; // field
  double gravity; // field
  // constructor
  public Planet (string n, double g) {
    name = n;
    gravity = g;
  }
  // property
  public string Name {
    get {return name;}
  }
  // property
  public double Gravity {
    get {return gravity;}
  }
}
Next, we define the Astronaut class. As with the Planet class, we first define our data members. Here an astronaut has two fields: the astronaut's fitness, and the current planet the astronaut is on. We then provide a constructor, which initializes the fitness of an astronaut. Next we define a CurrentPlanet property that allows us to get or set the planet an astronaut is on. Finally we define a jump method that outputs how far the astronaut jumps, based on the fitness of the astronaut and the planet he is on.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Classes
               attributes? unsafe? access-modifier?
new? 
[ abstract | sealed ]?
class class-name 
[: base-class | 
 : interface+ | 
 : base-class, interface+ ]?
{ class-members }
In C#, a program is built by defining new types, each with a set of data members and function members. Custom types should form higher-level building blocks that are easy to use, and closely model your problem space.
In this example, we simulate an astronaut jumping on different planets, using three classes: Planet, Astronaut, and Test to test our simulation.
First, let's define the Planet class. By convention, we define the data members of the class at the top of the class declaration. There are two data members here: the name and gravity fields, which store the name and gravity of a planet. We then define a constructor for the planet. Constructors are function members that allow you to initialize an instance of your class. We initialize the data members with values fed to the parameters of the constructor. Finally, we define two more function members, which are properties that allow us to get the "Name" and "Gravity" of a planet. The Planet class looks like this:
using System;
  
class Planet {
  string name; // field
  double gravity; // field
  // constructor
  public Planet (string n, double g) {
    name = n;
    gravity = g;
  }
  // property
  public string Name {
    get {return name;}
  }
  // property
  public double Gravity {
    get {return gravity;}
  }
}
Next, we define the Astronaut class. As with the Planet class, we first define our data members. Here an astronaut has two fields: the astronaut's fitness, and the current planet the astronaut is on. We then provide a constructor, which initializes the fitness of an astronaut. Next we define a CurrentPlanet property that allows us to get or set the planet an astronaut is on. Finally we define a jump method that outputs how far the astronaut jumps, based on the fitness of the astronaut and the planet he is on.
using System;
  
class Astronaut {
  double fitness; // field
  Planet currentPlanet; // field
  
  // constructor
  public Astronaut (double f) {
    fitness = f;
  }
  // property
  public Planet CurrentPlanet {
    get {
      return currentPlanet; 
    }
    set {
      currentPlanet = value;
    }
  }
  // method
  public void Jump () {
    if (currentPlanet == null)
      Console.WriteLine ("Bye Bye!");
    else {
      double distance = fitness/currentPlanet.Gravity;
      Console.WriteLine ("Jumped {0} metres on {1}", distance,
                         currentPlanet.Name);
    }
  }
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Inheritance
A class can inherit from another class to extend or customize the original class. Inheriting from a class allows you to reuse the functionality in that class instead of building it from scratch. A class can inherit from only a single class, but can itself be inherited by many classes, thus forming a class hierarchy. A well-designed class hierarchy is one that reasonably generalizes the nouns in a problem space. For example, there is a class called Imagein the System.Drawing namespace, which the Bitmap, Icon, and Metafile classes inherit from. All classes are ultimately part of a single giant class hierarchy, of which the root is the Object class. All classes implicitly inherit from it.
In this example, we start by defining a class called Location. This class is very basic, and provides a location with a name property and a way to display itself to the console window:
class Location { // Implicitly inherits from object
  string name;
  
  // The constructor that initializes Location
  public Location(string n) {
    name = n;
  }
  public string Name {get {return name;}}
  public void Display() {
    System.Console.WriteLine(Name);
  }
}
Next, we define a class called URL, which will inherit from Location. The URL class has all the same members as Location, as well as a new member, Navigate. Inheriting from a class requires specifying the class to inherit from the class declaration, using the C++ colon notation:
class URL : Location { // Inherit from Location
  public void Navigate() {
    System.Console.WriteLine("Navigating to "+Name);
  }
  // The constructor for URL, which calls Location's constructor
  public URL(string name) : base(name) {}
}
Now, we instantiate a URL, then invoke both the Display method (which is defined in Location) and the navigate method (which is defined in URL):
class Test {
  static void Main() {
    URL u = new URL("http://microsoft.com");
    u.Display();
    u.Navigate();
  }
}
The specialized class/general class is referred to as the derived class/base class or the subclass/superclass.
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 Modifiers
To promote encapsulation, a type or type member may hide itself from other types or other assemblies by adding one of the following five access modifiers to the declaration:
public
The type or type member is fully accessible. This is the implicit accessibility for enum members (see Section 3.6, later in this chapter) and interface members (see Section 3.5, later in this chapter).
internal
The type or type member in assembly A is accessible only from within A. This is the default accessibility for nonnested types, and so may be omitted.
private
The type member in type T is accessible only from within T. This is the default accessibility for class and struct members, and so may be omitted.
protected
The type member in class C is accessible only from within C, or from within a class that derives from C.
protected internal
The type member in class C and assembly A is accessible only from within C, from within a class that derives from C, or from within A. Note that C# has no concept of protected and internal, whereby "a type member in class C and assembly A is accessible only from within C, or from within a class that both derives from C and is within A."
Note that a type member may be a nested type. Here is an example of using access modifiers:
// Assembly1.dll
using System;
public class A {
  private int x=5;
  public void Foo() {Console.WriteLine (x);}
  protected static void Goo() {}
  protected internal class NestedType {}
}
internal class B {
  private void Hoo () {
    A a1 = new A (); // ok
    Console.WriteLine(a1.x); // error, A.x is private
    A.NestedType n; // ok, A.NestedType is internal
    A.Goo(); // error, A's Goo is protected
  }
}
  
// Assembly2.exe (references Assembly1.dll)
using System;
class C : A { // C defaults to internal
  static void Main() { // Main defaults to private
    A a1 = new A(); // ok
    a1.Foo(); // ok
    C.Goo(); // ok, inherits A's protected static member
    new A.NestedType(); // ok, A.NestedType is protected
    new B(); // error, Assembly 1's B is internal
    Console.WriteLine(x); // error, A's x is private
  }
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Structs
               attributes? unsafe? access-modifier?
new?
struct struct-name [: interface+]?
{ struct-members }
A struct is similar to a class, with the following major differences:
  • A class is a reference type, while a struct is a value type. Consequently, structs are typically used to express simple types, in which value-type semantics are desirable (e.g., assignment copies a value rather than a reference).
  • A class fully supports inheritance (see Chapter 5), whereas a struct can inherit only from an object and is implicitly sealed (in the runtime structs actually inherit from System.ValueType). Both classes and structs can implement interfaces.
  • A class can have a destructor, and a struct cannot.
  • A class can define a custom parameterless constructor and initialize instance fields, while a struct cannot. The default parameterless constructor for a struct initializes each field with a default value (effectively zero). If a struct declares a constructor(s), then all of its fields must be assigned in that constructor call.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Interfaces
               attributes? unsafe? access-modifier?
new?
interface interface-name 
[ : base-interface+ ]?
{ interface-members }
An interface is similar to a class, but with the following major differences:
  • An interface provides a specification rather than an implementation for its members. This is similar to a pure abstract class, which is an abstract class consisting of only abstract members.
  • A class and struct can implement multiple interfaces, while a class can inherit only from a single class.
  • A struct can implement an interface, but a struct cannot inherit from a class.
Earlier in this chapter, we defined polymorphism as the ability to perform the same operations on many types, as long as each type shares a common subset of characteristics. The purpose of an interface is precisely for defining such a set of characteristics.
An interface is comprised of a set of the following members:
  • Method
  • Property
  • Indexer
  • Event
These members are always implicitly public and implicitly abstract (and therefore virtual and nonstatic).
An interface declaration is like a class declaration, but it provides no implementation for its members, since all its members are implicitly abstract. These members are intended to be implemented by a class or struct that implements the interface. Here is a very simple interface that defines a single method:
public interface IDelete {
   void Delete();
}
Classes or structs that implement an interface may be said to "fulfill the contract of the interface." In this example, our
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Enums
               attributes? access-modifier?
new?
enum enum-name [ : integer-type ]?
{ [attributes? enum-member-name 
[ = value ]? ]* }
Enums specify a group of named numeric constants:
public enum Direction {North, East, West, South}
Unlike in C, enum members must be used with the enum type name. This resolves naming conflicts and makes code clearer:
Direction walls = Direction.East;
By default, enums are assigned integer constants 0, 1, 2, etc. You may optionally specify an alternative numeric type to base your enum on, and explicitly specify values for each enum member:
[Flags]
public enum Direction : byte {
   North=1, East=2, West=4, South=8
}
Direction walls = Direction.North | Direction.West;
if((walls & Direction.North) != 0)
    System.Console.WriteLine("Can't go north!");
The [Flags] attribute is optional, and informs the runtime that the values in the enum can be bit-combined, and should be decoded accordingly in the debugger or when outputting text to the console. For example:
Console.WriteLine(walls.Format()); // Displays "North|West"
Console.WriteLine(walls); // Calls walls.ToString, displays "5"
The System.Enum type also provides many useful static methods for enums that allow one to determine the underlying type of an enum, to check if a specific value is supported, to initialize an enum from a string constant, to retrieve a list of the valid values, and other common operations such as conversions. Here is an example of the usage:
using System;
public enum Toggle : byte { Off=0, On=1 }
class Test {
  static void Main() {
    Type t = Enum.GetUnderlyingType(typeof(Toggle));
    Console.WriteLine(t); // Prints "Byte"
  
    bool bDimmed = Enum.IsDefined(typeof(Toggle), "Dimmed");
    Console.WriteLine(bDimmed); // Prints "False"
  
    Toggle tog =(Toggle)Enum.Parse(typeof(Toggle), "On");
    Console.WriteLine(Enum.Format(typeof(Toggle), tog, "D")); // Prints "1"
    Console.WriteLine(tog); // Prints "On"
  
    object[] oa = Enum.GetValues(typeof(Toggle));
    foreach(Toggle toggle in oa) // Prints "On=1, Off=0"
      Console.WriteLine("{0}={1}", toggle,Enum.Format(typeof(Toggle), 
                                   toggle, "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 4: Advanced C# Features
In this chapter, we cover advanced C# topics, including events, operator overloading, try statements and exceptions, attributes, unsafe code and pointers, preprocessor directives, and XML documentation.
               attributes? unsafe? access-modifier?
new?
delegate
[ void | type ]
delegate-name (parameter-list);
A delegate is a type defining a method signature, so that delegate instances can hold and invoke a method or list of methods that match its signature. A delegate declaration consists of a name and a method signature. For example:
using System;
delegate bool Filter (string s);
  
class Test {
   static void Main() {
      Filter f = new Filter(FirstHalfOfAlphabet);
      Display(new String [] {"Ant","Lion","Yak"}, f);
   }
   static bool FirstHalfOfAlphabet(string s) {
      return "N".CompareTo(s) > 0;
   }
   static void Display(string[] names, Filter f) {
      int count = 0;
      foreach(string s in names)
         if(f(s)) // invoke delegate
            Console.WriteLine("Item {0} is {1}", count++, s);
   }
}
Note that the signature of a delegate method includes its return type. It also allows the use of a params modifier in its parameter list, which expands the list of elements that characterize an ordinary method signature. The actual name of the target method is irrelevant to the delegate.
Delegates can hold and invoke multiple methods. In this example, we declare a very simple delegate called MethodInvoker, which we use to hold and then invoke the Foo and Goo methods sequentially. The += method creates a new delegate by adding the right delegate operand to the left delegate operand:
using System;
delegate void MethodInvoker();
class Test {
   static void Main() {
       new Test(); // prints "Foo","Goo"
   }
   Test () {
      MethodInvoker m = null;
      m += new MethodInvoker(Foo);
      m += new MethodInvoker(Goo);
      m();
   }
   void Foo() {
      Console.WriteLine("Foo");
   }
   void Goo() {
      Console.WriteLine("Goo");
   }
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Delegates
               attributes? unsafe? access-modifier?
new?
delegate
[ void | type ]
delegate-name (parameter-list);
A delegate is a type defining a method signature, so that delegate instances can hold and invoke a method or list of methods that match its signature. A delegate declaration consists of a name and a method signature. For example:
using System;
delegate bool Filter (string s);
  
class Test {
   static void Main() {
      Filter f = new Filter(FirstHalfOfAlphabet);
      Display(new String [] {"Ant","Lion","Yak"}, f);
   }
   static bool FirstHalfOfAlphabet(string s) {
      return "N".CompareTo(s) > 0;
   }
   static void Display(string[] names, Filter f) {
      int count = 0;
      foreach(string s in names)
         if(f(s)) // invoke delegate
            Console.WriteLine("Item {0} is {1}", count++, s);
   }
}
Note that the signature of a delegate method includes its return type. It also allows the use of a params modifier in its parameter list, which expands the list of elements that characterize an ordinary method signature. The actual name of the target method is irrelevant to the delegate.
Delegates can hold and invoke multiple methods. In this example, we declare a very simple delegate called MethodInvoker, which we use to hold and then invoke the Foo and Goo methods sequentially. The += method creates a new delegate by adding the right delegate operand to the left delegate operand:
using System;
delegate void MethodInvoker();
class Test {
   static void Main() {
       new Test(); // prints "Foo","Goo"
   }
   Test () {
      MethodInvoker m = null;
      m += new MethodInvoker(Foo);
      m += new MethodInvoker(Goo);
      m();
   }
   void Foo() {
      Console.WriteLine("Foo");
   }
   void Goo() {
      Console.WriteLine("Goo");
   }
}
A delegate can also be removed from another delegate using the -= operator:
Test {
   MethodInvoker m = null;
   m += new MethodInvoker(Foo);
   m -= new MethodInvoker(Foo);
   // m is now null
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Delegates Versus Function Pointers
A delegate is behaviorally similar to a C function pointer (or Delphi closure), but delegates can hold multiple methods, as well as hold the instance associated with each nonstatic method. In addition, delegates, like all other C# constructs used outside unsafe blocks, are type-safe and secure. This means that you're protected from pointing to the wrong type of method or to a method you don't have permission to access.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Delegates Versus Interfaces
A problem that can be solved with a delegate can also be solved with an interface. For instance, the following explains how to solve our filter problem using an IFilter interface:
using System;
interface IFilter {
   bool Filter(string s);
}
class Test {
  class FirstHalfOfAlphabetFilter : IFilter {
    public bool Filter(string s) {
      return ("N".CompareTo(s) > 0);
    }      
  }
  static void Main() {
    FirstHalfOfAlphabetFilter f = new FirstHalfOfAlphabetFilter();
    Display(new string [] {"Ant", "Lion", "Yak"}, f);
  }
  static void Display(string[] names, IFilter f) {
    int count = 0;
    foreach (string s in names)
      if (f.Filter(s))
        Console.WriteLine("Item {0} is {1}", count++, s);
  }
}
In this case, the problem is slightly more elegantly handled with a delegate, but generally delegates are best used for event handling.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Events
Content preview·