BUY THIS BOOK

Safari Books Online

What is this?

Looking to Reprint this content?


C# Essentials
C# Essentials By Ben Albahari, Peter Drayton, Brad Merrill
February 2001
Pages: 216

Cover | Table of Contents | Colophon


Table of Contents

Chapter 1: Introduction
C# is a language built specifically to program the new Microsoft .NET Framework. The .NET Framework consists of a runtime environment called the Common Language Runtime (CLR), and a set of base class libraries, which provide a rich development platform that can be exploited by a variety of languages and tools.
Programming languages have strengths in different areas. Some languages are powerful but can be bug-prone and difficult to work with, while others are simpler but can be limiting in terms of functionality or performance. C# is a new language designed to provide an optimum blend of simplicity, expressiveness, and performance.
Many features of C# were designed in response to the strengths and weaknesses of other languages, particularly Java and C++. The C# language specification was written by Anders Hejlsberg and Scott Wiltamuth. Anders Hejlsberg is famous in the programming world for creating the Turbo Pascal compiler and leading the team that designed Delphi.
Key features of the C# language include the following:
Component orientation
An excellent way to manage complexity in a program is to subdivide it into several interacting components, some of which can be used in multiple scenarios. C# has been designed to make component building easy and provides component-oriented language constructs such as properties, events, and declarative constructs called attributes.
One-stop coding
Everything pertaining to a declaration in C# is localized to the declaration itself, rather than being spread across several source files or several places within a source file. Types do not require additional declarations in separate header or Interface Definition Language (IDL) files, a property's get/set methods are logically grouped, documentation is embedded directly in a declaration, etc. Furthermore, because declaration order is irrelevant types don't require a separate stub declaration to be used by another type.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
C# Language
Programming languages have strengths in different areas. Some languages are powerful but can be bug-prone and difficult to work with, while others are simpler but can be limiting in terms of functionality or performance. C# is a new language designed to provide an optimum blend of simplicity, expressiveness, and performance.
Many features of C# were designed in response to the strengths and weaknesses of other languages, particularly Java and C++. The C# language specification was written by Anders Hejlsberg and Scott Wiltamuth. Anders Hejlsberg is famous in the programming world for creating the Turbo Pascal compiler and leading the team that designed Delphi.
Key features of the C# language include the following:
Component orientation
An excellent way to manage complexity in a program is to subdivide it into several interacting components, some of which can be used in multiple scenarios. C# has been designed to make component building easy and provides component-oriented language constructs such as properties, events, and declarative constructs called attributes.
One-stop coding
Everything pertaining to a declaration in C# is localized to the declaration itself, rather than being spread across several source files or several places within a source file. Types do not require additional declarations in separate header or Interface Definition Language (IDL) files, a property's get/set methods are logically grouped, documentation is embedded directly in a declaration, etc. Furthermore, because declaration order is irrelevant types don't require a separate stub declaration to be used by another type.
Versioning
C# provides features such as explicit interface implementations, hiding inherited members, and read-only modifiers, which help new versions of a component work with older components that depend on it.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Common Language Runtime
Of fundamental importance to the .NET framework is the fact that programs are executed within a managed execution environment provided by the Common Language Runtime. The CLR greatly improves runtime interactivity between programs, portability, security, development simplicity, cross-language integration, and provides an excellent foundation for a rich set of class libraries.
Absolutely key to these benefits is the way .NET programs are compiled. Each language targeting .NET compiles source code into metadata and Microsoft Intermediate Language (MSIL) code. Metadata includes a complete specification for a program including all its types, apart from the actual implementation of each function. These implementations are stored as MSIL, which is machine-independent code that describes the instructions of a program. The CLR uses this "blueprint" to bring a .NET program to life at runtime, providing services far beyond what is possible with the traditional approach—compiling code directly to assembly language.
Key features of the CLR include the following:
Runtime interactivity
Programs can richly interact with each other at runtime through their metadata. A program can search for new types at runtime, then instantiate and invoke methods on those types.
Portability
Programs can be run without recompiling on any operating system and processor combination that supports the CLR. A key element of this platform independence is the runtime's JIT ( Just-In-Time Compiler), which compiles the MSIL code it is fed to native code that runs on the underlying platform.
Security
Security considerations permeate the design of the .NET Framework. Key to making this possible is CLR's ability to analyze MSIL instructions as being safe or unsafe.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Base Class Libraries
The .NET Framework provides the Base Class Libraries (BCL), which can be used by all languages. These libraries range from those that are a portal to core functionality of the runtime, such as threading and runtime manipulation of types (reflection), to libraries that provide high-level functionality, such as data access, rich client support, and web services (whereby code can even be embedded in a web page). C# has almost no built-in libraries; it uses the BCL instead.
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 Test {
      static void Main( ) {
         Console.WriteLine("Welcome to C#!");
      }
   }
}
A C# program is composed of types (typically classes) that we organize into namespaces. Each type contains function members (typically methods), as well as data members (typically fields). In our program, we define a class named Test that contains a method named Main, that writes Welcome to C#! to the Console window. The Console class encapsulates standard input/output functionality, providing methods such as WriteLine. To use types from another namespace, we use the using directive. Since the Console class resides in the System namespace, we go using System; similarily, types from other namespaces could use our Test class by going using FirstProgram.
In C#, there are no standalone functions; functions 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. Of final note is that C# recognizes a method named Main as a default entry point of execution.
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 Reference
This chapter walks you through each aspect of the C# language. Many features of C# will be familiar if you have experience with a strongly typed object-oriented language.
Identifiers are names programmers choose for their types, methods, variables, and so on. An identifier must be a whole word, essentially composed of Unicode characters starting with a letter or underscore. An identifier must not clash with a keyword. As a special case, the @ prefix can be used to avoid such a conflict, but the character isn't considered part of the identifier that it precedes. For instance, the following two identifiers are equivalent:
C# identifiers are case-sensitive, but for compatibility with other languages, you should not differentiate public or protected identifiers by case alone.
A C# program is written by building new types and leveraging existing types, either those defined in the C# language itself or imported from other libraries. Each type contains a set of data and function members, which combine to form the modular units that are the key building blocks of a C# program.
Generally, you must create instances of a type to use that type. Those data members and function members that require a type to be instantiated are called instance members. Data members and function members that can be used on the type itself are called static members .
In this program, we build our own type called Counter and another type called Test that uses instances of the Counter. The Counter type uses the predefined type int, and the Test type uses the static function member WriteLine of the Console class defined in the System namespace:
// Imports types from System namespace, such as Console
using System;
class Counter { // New types are typically classes or structs
  // --- Data members ---
  int value; // field of type int
  int scaleFactor; // field of type int

  // Constructor, used to initialize a type instance
  public Counter(int scaleFactor) { 
    this.scaleFactor = scaleFactor;  
  }
  // Method
  public void Inc( ) {
    value+=scaleFactor;
  }
  // Property
  public int Count {
    get {return value; }
  }
}
class Test {
  // Execution begins here
  static void Main( ) {

    // Create an instance of counter type
    Counter c = new Counter(5);
    c.Inc( );
    c.Inc( );
    Console.WriteLine(c.Count); // prints "10";

    // Create another instance of counter type
    Counter d = new Counter(7);
    d.Inc( );
    Console.WriteLine(d.Count); // prints "7";
   }
}
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
Identifiers are names programmers choose for their types, methods, variables, and so on. An identifier must be a whole word, essentially composed of Unicode characters starting with a letter or underscore. An identifier must not clash with a keyword. As a special case, the @ prefix can be used to avoid such a conflict, but the character isn't considered part of the identifier that it precedes. For instance, the following two identifiers are equivalent:
C# identifiers are case-sensitive, but for compatibility with other languages, you should not differentiate public or protected identifiers by case alone.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Types
A C# program is written by building new types and leveraging existing types, either those defined in the C# language itself or imported from other libraries. Each type contains a set of data and function members, which combine to form the modular units that are the key building blocks of a C# program.
Generally, you must create instances of a type to use that type. Those data members and function members that require a type to be instantiated are called instance members. Data members and function members that can be used on the type itself are called static members .
In this program, we build our own type called Counter and another type called Test that uses instances of the Counter. The Counter type uses the predefined type int, and the Test type uses the static function member WriteLine of the Console class defined in the System namespace:
// Imports types from System namespace, such as Console
using System;
class Counter { // New types are typically classes or structs
  // --- Data members ---
  int value; // field of type int
  int scaleFactor; // field of type int

  // Constructor, used to initialize a type instance
  public Counter(int scaleFactor) { 
    this.scaleFactor = scaleFactor;  
  }
  // Method
  public void Inc( ) {
    value+=scaleFactor;
  }
  // Property
  public int Count {
    get {return value; }
  }
}
class Test {
  // Execution begins here
  static void Main( ) {

    // Create an instance of counter type
    Counter c = new Counter(5);
    c.Inc( );
    c.Inc( );
    Console.WriteLine(c.Count); // prints "10";

    // Create another instance of counter type
    Counter d = new Counter(7);
    d.Inc( );
    Console.WriteLine(d.Count); // prints "7";
   }
}
Each type has its own set of rules defining how it can be converted to and from other types. Conversions between types may be implicit or 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!
Variables
A variable represents a typed storage location. A variable can be a local variable, a parameter, an array element (see Section 2.11), an instance field, or a static field (see Section 2.9.2).
Every variable has 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 is enforced at compile time, rather than runtime. In addition, C# is type-safe, which, with the help of runtime checking, ensures that a variable can be operated on only via the correct type (except in unsafe blocks; see Section 2.17.2).
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[] iarr = new Test [2]; // declare array
    Console.WriteLine(iarr[1]); // ok, elements assigned to null
    Test t;
    Console.WriteLine(t); // error, t not assigned
  }
}
The compiler generates a warning whenever a default value is assigned to a field that it can prove was never explicitly assigned. In the previous example, a warning is generated if the line v = a is commented out.
The following table shows that, essentially, the default value for all primitive (or atomic) types is zero:
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) 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 their order of evaluation. Binary operators (except for assignment operators) are left-associative and are evaluated from left to right. The assignment operators, unary operators, and the conditional operator are right-associative , evaluated from right to left.
For example:
1 + 2 + 3 * 4
is evaluated as:
((1 + 2) +(3 * 4))
because * has a higher precedence than +, and + is a left-associative binary operator. You can insert brackets to change the default order of evaluation. C# also overloads operators, which means the same operator symbols can have different meanings in different contexts (e.g., primary, unary, etc.) or different meanings for different types.
Table 2.2 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 (see Section 2.9.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!
Statements
Execution of 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. The two most basic kinds of statement in C# are the declaration and expression statements. C# also provides flow control statements for selection, looping and jumping. Finally C# provides statements for special purposes, such as locking memory or handling exceptions.
So that multiple statements can be grouped together, zero or more statements may be enclosed in braces ({ and }), to form a statement block. A statement block can be used anywhere a single statement is valid.
[ variable =]? expression ;
An expression statement evaluates an expression either assigning its result to a variable or generating side-effects, (i.e., invocation, new, ++, or --). 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
Variable declaration syntax:
type [ variable [ = expression ]?]+ ;
Constant declaration syntax:
const type [ variable = constant - expression ]+ ;
A declaration statement declares a new variable. You can initialize a variable at the time of its declaration by optionally assigning it the result of an expression.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Organizing Types
A C# program is basically a group of types. These types are defined in files, organized by namespaces, compiled into modules, and then grouped into an assembly.
Generally, these organizational units overlap: an assembly can contain many namespaces, and a namespace can be spread across several assemblies. A module can be part of many assemblies, and an assembly can contain many modules. A source file can contain many namespaces, and a namespace can span many source files. For more information, see Section 3.9 in Chapter 3.
File organization is of almost no significance to the C# compiler: an entire project can be merged into a single .cs file and still compile successfully (preprocessor statements are the only exception to this). However, it's generally tidy to have one type in one file, with a filename that matches the name of the class and a directory name that matches the name of the class's namespace.
Namespace declaration syntax:
namespace name + {
using-statement *
[ namespace-declaration | type-declaration ]*
}
Dot-delimited.
No delimiters.
A namespace enables you to group related types into a hierarchical categorization. Generally the first name in a namespace name is the name of your organization, followed by names that group types with finer granularity. For example:
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 C# class can inherit from another class to extend or customize that class. A class can only inherit from a single class but can be inherited by many classes, thus forming a class hierarchy. At the root of any class hierarchy is the object class, which all objects implicitly inherit from. Inheriting from a class requires specifying the class to inherit from in the class declaration, using the C++ colon notation:
class Location { // Implicitly inherits from object
  string name;

  // The constructor that initializes Location
  public Location(string name) {
    this.name = name;
  }
  public string Name {get {return name;}}
  public void Display( ) {
    Console.WriteLine(Name);
  }
}
class URL : Location { // Inherit from Location
  public void Navigate( ) {
    Console.WriteLine("Navigating to "+Name);
  }
  // The constructor for URL, which calls Location's constructor
  public URL(string name) : base(name) {}
}
URL has all the members of Location, and a new member, Navigate:
class Test {
  static void Main( ) {
    URL u = new URL("http://microsoft.com");
    u.Display( );
    u.Navigate( );
  }
}
The specialized class and general class are referred to as either the derived class and base class or the subclass and superclass .
A class D may be implicitly upcast to the class B it derives from, and a class B may be explicitly downcast to a class D that derives from it. For instance:
URL u = new URL( );
Location l = u; // upcast
u = (URL)l; // downcast
If the downcast fails, an InvalidCastException is thrown.

Section 2.7.1.1: as operator

The as operator allows a downcast to be made that evaluates to null if the downcast fails:
u = l as URL;
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 2.12) and interface members (see Section 2.10).
internal
The type or type member in assembly A is accessible only from within A. This is the default accessibility for nonnested types, 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, so it 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, where a type member in class C and assembly A is accessible only from within C or from within a class that derives from C and is within A.
Note that a type member may be a nested type. Here is an example that uses 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!
Classes and Structs
Class declaration syntax:
attributes ? access-modifier ?
new? [ abstract | sealed ]?
class class-name [
: base-class | : interface + | : base-class , interface + ]?
{ class-members }
Struct declaration syntax:
attributes ? access-modifier ?
new?
struct struct-name [ : interface + ]?
{ struct-members }
A class or struct combines data, functions, and nested types into a new type, which is a key building block of C# applications. The body of a class or struct is comprised of three kinds of members: data, function, and type.
Data members
Includes fields, constants, events. The most common data members are fields. Events are a special case, since they combine data and functionality in the class or struct (see Section 2.14).
Function members
Includes methods, properties, indexers, operators, constructors, and destructors. Note that all function members are either specialized types of methods or are implemented with one or more specialized types of methods.
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 ? access-modifier ?
new?
interface interface-name [ : base-interface + ]?
{ interface-members }
An interface is like a class, but with these 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; a class can inherit only from a single class.
  • A struct can implement an interface; a struct can't inherit from a class.
In Section 2.9, 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 comprises one or more methods, properties, indexers, and events. These members are always implicitly public and implicitly abstract (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's a 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, GUI controls that support the concept of deleting, such as a
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 + ][*]*;
Note: [*] is the set: [] [,] [,,] ...
Arrays allow a group of elements of a particular type to be stored in a contiguous block of memory. Array types derive from System.Array and are declared in C# using left and right brackets ([]). For instance:
char[] vowels = new char[] {'a','e','i','o','u'};
Console.WriteLine(vowels [1]); // Prints "e"
The preceding function call prints "e" because array indexes start at 0. To support other languages, .NET can create arrays based on arbitrary start indexes, but the BCL libraries always use zero-based indexing. Once an array has been created, its length can't be changed. However, the System.Collection classes provide dynamically sized arrays, as well as other data structures, such as associative (key/value) arrays (see Section 3.4 in Chapter 3).
Multidimensional arrays come in two varieties, rectangular and jagged. Rectangular arrays represent an n-dimensional block; jagged arrays are arrays of 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 can omit the array type when assigning a known value, because the type is specified in the declaration:
int[,] array = {{1,2},{3,4}};
Arrays know their own length. For multidimensional array methods, the array
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 can 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. It 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 you 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:
using System;
public enum Toggle : byte { Off=0, On=1 }
class TestEnum {
  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 =(Switch)Enum.FromString(typeof(Toggle), "On");
    Console.WriteLine(tog); // Prints "1"
    Console.WriteLine(tog.Format( )); // Prints "On"

    object[] oa = Enum.GetValues(typeof(Toggle));
    foreach(Toggle tog in oa) // Prints "On=1, Off=0"
      Console.WriteLine("{0}={1}", tog.Format( ), tog); 
  }
}
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 ? access-modifier ?
new?
delegate
[ void | type ]
delegate - name ( parameter-list );
A delegate is a type that defines 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.
Here's an example:
delegate bool Filter(string s);
This declaration lets you create delegate instances that can hold and invoke methods that return bool and have a single string parameter. In the following example a Filter is created that holds the FirstHalf-OfAlphabet method. You then pass the Filter to the Display method, which invokes the Filter:
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);
   }
}
If a delegate has a void return type, it is a multicast delegate that can hold and invoke multiple methods. In this example, we declare a simple delegate called MethodInvoker, which can 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.
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!
Events
Event handling is essentially a process by which one object can notify other objects that an event has occurred. This process is largely encapsulated by multicast delegates, which have this ability built-in.
The .NET Framework defines many event-handling delegates for you, but you can write your own. For example:
delegate void MoveEventHandler(object source, MoveEventArgs e);
By convention, an event delegate's first parameter denotes the source of the event, and the delegate's second parameter derives from System. EventArgs and stores data about the event.
The EventArgs class can be derived from to include information relevant to a particular event:
public class MoveEventArgs : EventArgs {
  public int newPosition;
  public bool cancel;
  public MoveEventArgs(int newPosition) {
    this.newPosition = newPosition;
  }
}
A class or struct can declare an event by applying the event modifier to a delegate field. In this example, the Slider class has a Position property that fires a Move event whenever its Position changes:
class Slider {
  int position;
  public event MoveEventHandler Move;
  public int Position {
    get { return position; }
    set {
      if (position != value) { // if position changed
        if (Move != null) { // if invocation list not empty
          MoveEventArgs args = new MoveEventArgs(value);
          Move(this, args); // fire event
	 if (args.cancel)
            return;
        }
        position = value;
      }
    }  
  }
}
The event keyword promotes encapsulation by ensuring that only the += and -= operations can be performed on the delegate. This means other classes can register themselves to be notified of the event, but only the Slider can invoke the delegate (fire the event) or clear the delegate's invocation list.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
try Statements and Exceptions
try statement-block
[catch (exception type value ?)? statement-block ]+ |
finally statement-block |
[catch (exception type value ?)? statement-block ]+
finally statement-block
The purpose of a try statement is to simplify dealing with program execution in exceptional circumstances. A try statement does two things. First, it lets exceptions thrown during the try block's execution be caught by the catch block. Second, it ensures that execution can't leave the try block without first executing the finally block. A try block must be followed by one or more catch blocks, a finally block, or both.
C# exceptions are objects that contain information representing the occurrence of an exceptional program state. When an exceptional state has occurred (e.g., a method receives an illegal value), an exception object may be thrown, and the call-stack is unwound until the exception is caught by an exception handling block. Here's an example:
public class File {
  ...
  public static StreamWriter CreateText(string s) {
    ...
    if (!Valid(s))      
      throw new IOException("Couldn't create...", ...);
      ...
  }
}
class Test {
  ...
  void Foo(object x) {
    StreamWriter sw = null;
    try {
      sw = File.CreateText("foo.txt");
      sw.Write(x.ToString( ));
    }
    catch(IOException ex) {
      Console.WriteLine(ex);
    }
    finally {
      if(sw != null)
        sw.Close( );
    }
  }
}
A catch clause specifies what exception type (including derived types) to catch. An exception must be of type
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Attributes
[[ target :]? attribute-name (
positional-param+ |
[ named-param = expression ]+ |
positional-param +, [ named-param = expression ] + )?]
Attributes are language constructs that can decorate a code element (assemblies, modules, types, members, return values, and parameters) with additional information.
In every language, you specify information associated with the types, methods, parameters, and other elements of your program. For example, a type can specify a list of interfaces that it derives from, or a parameter can specify how its values are to be passed with modifiers such as the ref modifier in C#. The limitation of this approach is that you can only associate information with code elements using the predefined constructs that the language itself provides.
Attributes allow programmers to add to the types of information associated with these code elements. For example, serialization in the .NET Framework uses various serialization attributes applied to types and fields to define how these code elements are serialized. This is more flexible than requiring the language to have special syntax for serialization.
An attribute is defined by a class that inherits (directly or indirectly) from the abstract class System.Attribute . When specifying an attribute on an element, the attribute name is the name of the type. By convention the derived type name ends with the word "Attribute", but this suffix isn't required.
In this example we specify that the Foo class is serializable using the Serializable attribute:
[Serializable]
public class Foo {...}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Unsafe Code and Pointers
C# supports direct memory manipulation via pointers within blocks of code marked unsafe. Pointer types are primarily useful for interoperability with C APIs but may also be used for accessing memory outside the managed heap or for performance-critical hotspots.
For every value type or pointer type V in a C# program, there is a corresponding C# pointer type named V*. A pointer instance holds the address of a value. That value is considered to be of type V, but pointer types can be (unsafely) cast to any other pointer type. Table 2.3 summarizes the principal pointer operators supported by the C# language.
Table 2.3: Principal Pointer Operators
Operator
Meaning
                              
                              &
The address-of operator returns a pointer to the address of a value.
                              
                              *
The dereference operator returns the value at the address of a pointer.
                              
                              ->
The pointer-to-member operator is a syntactic shortcut, where
x->y is equivalent to (*x).y.
Methods, statement blocks, or single statements can be marked with the unsafe keyword to perform C++-style pointer operations on memory. Here is an example that uses pointers with a managed object:
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Preprocessor Directives
Preprocessor directives supply the compiler with additional information about regions of code. The most common preprocessor directives are the conditional directives, which provide a way to include or exclude regions of code from compilation. For example:
#define DEBUG
class MyClass {
  int x;
  void Foo( ) {
  # if DEBUG
    Console.WriteLine("Testing: x = {0}", x);
  # endif
  ...
}
In this class, the statement in Foo is compiled conditionally, dependent upon the presence of the user-selected DEBUG symbol. If you remove the DEBUG symbol, the statement isn't compiled. Preprocessor symbols can be defined within a source file as just shown, and they can be passed to the compiler with the /define : symbol command-line option. All preprocessor symbols are implicitly true, so the previous #define statement is effectively identical to:
#define DEBUG = true
The #error and #warning symbols prevent accidental misuse of conditional directives by making the compiler generate a warning or error given an undesirable set of compilation symbols.
The C# language supports the preprocessor directives shown in Table 2.4.
Table 2.4: Preprocessor Directives
Preprocessor Directive
Action
#define symbol
Defines symbol
#undef symbol
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
XML Documentation
C# offers three different styles of source-code documentation: single-line comments, multiline comments, and documentation comments.
Single- and multiline comments use the C++ syntax: // and /*...*/:
int x = 3; // this is a comment
MyMethod( ); /* this is a
comment that spans two lines */
The disadvantage of this style of commenting is that there is no predetermined standard for documenting your types. Consequently, it can't be easily parsed to automate the production of documentation. C# improves on this by allowing you to embed documentation comments in the source, and by providing an automated mechanism for extracting and validating documentation at compile time.
Documentation comments are similar to C# single-line comments but start with /// and can be applied to any user-defined type or member. These comments can include embedded XML tags as well as descriptive text. These tags allow you to mark up the descriptive text to better define the semantics of the type or member and also to incorporate cross-references.
These comments can then be extracted at compile time into a separate output file containing the documentation. The compiler validates the comments for internal consistency, expands cross references into fully qualified type IDs, and outputs a well-formed XML file. Further processing is left up to you, although a common next step is to run the XML through an XSL/T, generating HTML documentation.
Here is an example documentation for a simple type:
// Filename: DocTest.cs
using System;
class MyClass {
  /// <summary>
  /// The Foo method is called from
  ///   <see cref="Main">Main</see> 
  /// </summary>
  /// <mytag>Secret stuff</mytag>
  /// <param name="s">Description for s</param>
  static void Foo(string s) { Console.WriteLine(s); }
  static void Main( ) { Foo("42"); }
}
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: Programming the .NET Framework