BUY THIS BOOK
Add to Cart

PDF $6.99

Safari Books Online

What is this?

Looking to Reprint or License this content?


C# Language Pocket Reference
C# Language Pocket Reference By Peter Drayton, Ben Albahari, Ted Neward
October 2002
Pages: 128

Cover | Table of Contents


Table of Contents

Chapter : C# Language Pocket Reference
C# is a programming language from Microsoft that is designed specifically to target the .NET Framework. Microsoft's .NET Framework is a runtime environment and class library that dramatically simplifies the development of modern, component-based applications.
Microsoft has shown an unprecedented degree of openness in C# and the .NET Framework. The key specifications for the C# language and the .NET platform have been published, reviewed, and ratified by an international standards organization called the European Computer Manufacturers Association (ECMA). This standardization effort has led to a Shared Source release of the specification called the Shared Source CLI (http://msdn.microsoft.com/net/sscli/), as well as to Open Source implementations of .NET called DotGNU Portable .NET (http://www.dotgnu.org) and Mono (http://www.go-mono.com). All three implementations include support for C#.
This book is a quick-reference manual to the C# language as of version 1.0 of the .NET Framework. It lists a concise description of language syntax and provides a guide to other areas of the .NET Framework that are of interest to C# programmers.
The purpose of this quick reference is to aid readers who need to look up some basic detail of C# syntax or usage. It is not intended to be a tutorial or user guide, and at least a basic familiarity with C# is assumed. If you'd like more in-depth information or a more detailed reference, please see Programming C# by Jesse Liberty and C# in a Nutshell by Drayton, Albahari, and Neward (both O'Reilly, 2002).
Identifiers are names programmers choose for their types, methods, variables, etc. An identifier must be a whole word that is essentially made up of Unicode characters starting with a letter or an underscore, and it 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:
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 that is essentially made up of Unicode characters starting with a letter or an underscore, and it 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:
C# identifiers are case-sensitive; however, 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!
Fundamental Elements
A C# program is best understood in terms of three basic elements:
Functions
Perform 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
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 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.
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 and Reference Types
All C# types fall into the following categories:
  • Value types (struct, enum)
  • Reference types (class, array, delegate, interface)
The fundamental difference between the two main categories is how they are handled in memory. The following sections explain the essential differences between value types and reference types.
Value types directly contain data, such as the int type (which holds an integer) or the bool type (which holds a true or false value). The key characteristic of a value type is a copy made of the value that is assigned to another 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 defines two separate entities: an object and a reference to that object. This example follows the same pattern as our previous example, except that the variable y is updated here, while y remained unchanged earlier:
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 different things, which can be separated into these two lines:
StringBuilder x;
x = new StringBuilder ("hello");
The first line creates a new variable that can hold a reference to a StringBuilder object. The second line assigns a new StringBuilder object to the variable. Let's look at the next line:
StringBuilder y = x;
When we assign
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
All of C#'s predefined 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
System.Int16
2 bytes
Yes
int
System.Int32
4 bytes
Yes
long
System.Int64
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
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:
int[] nums = new int[2];
nums[0] = 100;
nums[1] = 200;
char[ ] vowels = new char[ ] {'a','e','i','o','u'};
Console.WriteLine(vowels [1]); // Prints "e"
That last line 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 is 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 later in Section 1.8. 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, instance field, or 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 via the correct type only with the help of runtime checking (except in unsafe blocks).
All variables in C# 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
  }
}
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 than +, 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 can have different meanings for different types.
Table 1-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.
Table 1-2: Operator precedence table
Category
Operators
Examples
Primary
Grouping:
Member 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!
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.
An expression statement evaluates an expression, either assigning its result to a variable or generating side effects (i.e., method invocation, new, ++, --). An expression statement ends in a semicolon. For example:
int x = 5 + 6; // assign result
x++; // side effect
int 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
A declaration statement declares a new variable, optionally assigning the result of an expression to that variable. A declaration statement ends in a semicolon:
int x = 100; // variable declaration
const int y = 110; // constant declaration
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
C# has many ways to control the flow of program execution conditionally. This section covers the simplest two constructs: 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!
Namespaces
These are defined in files, organized by namespaces, compiled into a module, then grouped into an assembly. These organizational units are cross-cutting. For example, typically a group of namespaces belong to one assembly, but a single namespace may in fact be spread over multiple assemblies.
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 file's directory matching the name of the class's namespace.
A namespace lets you group related types into a hierarchical categorization. Generally, the first name is the name of your organization; it gets more specific from there:
namespace MyCompany.MyProduct.Drawing {
  class Point {int x, y, z;}
  delegate void PointInvoker(Point p);
}

Section 1.9.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 1.9.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 that a type is within actually becomes part of the type name:
namespace TestProject {
  class Test {
    static void Main( ) {
      MyCompany.MyProduct.Drawing.Point x;
    }
  }
}

Section 1.9.2.3: The using keyword

The using keyword is a convenient way to avoid using the fully qualified name of types in other namespaces. This example is semantically identical to our previous 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!
Classes
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 that 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!
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 the later section Section 1.14) and interface members (see the later section Section 1.13).
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 it 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 it may be omitted.
protected
The type member in class C is accessible 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 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
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., an assignment copies a value rather than a reference).
  • A class fully supports inheritance, 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.
Here is a simple struct declaration:
struct Point {
  public int x, y;
}
To create a struct, you can use the new operator, which will initialize all the struct members to their defaults (zero in the case of x and y). If you do not use the new operator, you will need to initialize the struct members yourself. You can also use array declaration syntax to create an array of structs:
Point p1 = new Point(  );
Point p2;
p2.x = p2.y = 0;
Point[] points = new Point[3];
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
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 consists only of 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.
Polymorphism is described 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 IDelete interface can be implemented by GUI controls that support the concept of deleting, such as a TextBox, TreeView, or your own custom GUI control.
public class TextBox : IDelete {
  public void Delete( ) {...}
}
public class TreeView : IDelete {
  public void Delete( ) {...}
}
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
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 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); // Displays "North, West"
Console.WriteLine((int) walls); // Displays "5"
The System.Enum type also provides many useful static methods for enums that let you determine the underlying type of an enum, check if a specific value is supported, initialize an enum from a string constant, 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"
  
    Array 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!
Delegates
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!
Events
Event handling is essentially a process in 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 provides many event-handling delegates, but you can write your own. For example:
public delegate void MoveEventHandler(object source, MoveEventArgs e);
By convention, the delegate's first parameter denotes the source of the event, and the delegate's second parameter derives from System.EventArgs and contains data about the event.
The EventArgs class may be derived from to include information relevant to a particular event:
using System;
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:
public 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. Other classes may act on the event, but only the Slider can invoke the delegate (fire the event) or clear the delegate's invocation list.
We are able to act on an event by adding an event handler to it. An event handler is a delegate that wraps the method we want invoked when the event is fired.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Operator Overloading
C# lets you overload operators to work with operands that are custom classes or structs using operators. An operator is a static method with the keyword operator preceding the operator to overload (instead of a method name), parameters representing the operands, and return types representing the result of an expression. Table 1-3 lists the available overloadable operators.
Table 1-3: Overloadable operators
+
-
!
~
++
--
* (binary only)
/
%
& (binary only)
|
^
<<
>>
==
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
The purpose of a try statement is to simplify program execution in exceptional circumstances—typically, an error. A try statement does two things. First, it lets the catch block catch exceptions thrown during the try block's execution. Second, it ensures that execution cannot leave the try block without first executing the finally block. A try block must be followed by a catch block(s), a finally block, or both. The form of a try block looks like this:
try {
  ... // exception may be thrown during execution of this
      // function
}
catch (ExceptionA ex) {
  ... // react to exception of type ExceptionA
}
catch (ExceptionB ex) {
  ... // react to exception of type ExceptionB
}
finally {
  ... // code to always run after try block executes, even if
  ... // an exception is not thrown
}
C# exceptions are objects that contain information representing the occurrence of an exceptional program state. When an exceptional state occurs (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. For example:
using System;
public class WeightCalculator {
  public static float CalcBMI (float weightKilos, 
                               float metersTall) {
    if (metersTall < 0 || metersTall > 3)
      throw new ArgumentException ("Impossible Height",
                                   "metersTall");
    if (metersTall < 0 || weightKilos > 1000)
      throw new ArgumentException ("Impossible Weight",
                                   "weightKilos");
    return weightKilos / (metersTall*metersTall);
  }
}
class Test {
  static void Main ( ) {
    TestIt ( );
  }
  static void TestIt ( ) {
    try {
      float bmi = WeightCalculator.CalcBMI (100, 5);
      Console.WriteLine(bmi);
    }
    catch(ArgumentException ex) {
      Console.WriteLine(ex);
    }
    finally {
      Console.WriteLine (
        "Thanks for running the program");
    }
    Console.Read( );
  }
}
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
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 from which it derives, or a parameter can specify modifiers, such as the ref modifier in C#. The limitation of this approach is that you can associate information with code elements using only the predefined constructs that the language provides.
Attributes allow programmers to extend 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 approach 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 to an element, the attribute name is the name of the type. By convention, the derived type name ends in Attribute, although specifying the suffix is not required when specifying the attribute.
In this example, the Foo class is specified as serializable using the Serializable attribute:
[Serializable]
public class Foo {...}
The Serializable attribute is actually a type declared in the System namespace, as follows:
class SerializableAttribute : Attribute {...}
We could also specify the Serializable attribute using its fully qualified type name, as follows:
[System.SerializableAttribute]
public class Foo {...}
The preceding two examples of using the Serializable attribute are semantically identical.
The C# language and the FCL include a number of predefined attributes. For more information about the other attributes included in the FCL and about creating your own attributes, see the "Writing Custom Attributes" topic in the .NET Framework SDK Documentation.
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 and compiled with the /unsafe compiler option. Pointer types are primarily useful for interop 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, there is a corresponding pointer type V*. A pointer instance holds the address of a value. This is considered to be of type V, but pointer types can be (unsafely) cast to any other pointer type. Table 1-4 lists the main pointer operators.
Table 1-4: 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, in which x->y is equivalent to (*x).y
By marking a type, type member, or statement block with the unsafe keyword, you're permitted to use pointer types and perform C++-style pointer operations on memory within that scope. Here is an example of using 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
using System;
class MyClass {
  static int x = 5;
  static void Main( ) {
  # if DEBUG
    Console.WriteLine("Testing: x = {0}", x);
  # endif
  }
}
In this class, the statement in Foo is compiled as conditionally dependent upon the presence of the DEBUG symbol. If we remove the DEBUG symbol, the statement is not compiled. Preprocessor symbols can be defined within a source file (as we have done) and can be passed to the compiler with the /define: symbol command-line option.
The #error and #warning symbols prevent accidental misuse of conditional directives by making the compiler generate a warning or error when given an undesirable set of compilation symbols. See Table 1-5 for a list of preprocessor directives and their actions.
Table 1-5: Preprocessor directives
Preprocessor directive
Action
#define symbol
Defines symbol
#undef symbol
Undefines symbol
#if symbol [ operator
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Framework Class Library Overview
Almost all the capabilities of the .NET Framework are exposed via a set of managed types known as the Framework Class Library (FCL). Because these types are CLS compliant, they are accessible from almost any .NET language. FCL types are grouped logically by namespace and are exported from a set of assemblies that are part of the .NET platform. Using these types in a C# application requires you to reference the appropriate assembly when compiling (most essential assemblies are referenced by default; see Section1.23 later in this book). For you to work effectively in C# on the .NET platform, it is important to understand the general capabilities in the predefined class library.
In this section, we give an overview of the entire FCL (broken down by logical area) and provide references to relevant types and namespaces so that you can explore their details in the .NET Framework SDK on your own.
The specific types and namespaces mentioned in this overview are based on the final released version of the .NET Framework.
Useful tools for exploring the FCL include the .NET Framework SDK documentation, the Visual Studio .NET documentation, the WinCV.exe class browser, and the ILDasm.exe disassembler.
The core types are contained in the System namespace. This namespace is the heart of the FCL and contains classes, interfaces, and attributes on which all other types depend. The root of the FCL is the type Object , from which all other .NET types derive. Other fundamental types are ValueType (the base type for structs), Enum (the base type for enums), Convert (used to convert between base types), Exception (the base type for all exceptions), and the boxed versions of the predefined value types. Interfaces that are used throughout the FCL (such as ICloneable, IComparable, IFormattable, and IConvertible) are also defined here. Extended types such as
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 and Assemblies
Table 1-6 allows you to look up a namespace and determine which assemblies export that namespace. This information is helpful when constructing the appropriate /reference:<file list> command-line option for the C# compiler. However, commonly used assemblies are referenced by default.
For a complete list of default assemblies, see the global C# response file, csc.rsp , in %SystemRoot%\Microsoft.NET\Framework\VERSION, where VERSION is the version number of the framework (the first release of .NET is v1.0.3705). You can modify csc.rsp to affect all compilations that run on your machine, or you can create a local csc.rsp in your current directory. The local response file is processed after the global one. You can use the /noconfig switch with csc.exe to disable the local and global csc.rsp files entirely.
Table 1-6: Namespace and assembly cross-reference
Namespace
DLLs
Accessibility
Accessibility.dll
EnvDTE
envdte.dll
IEHost.Execute
IEExecRemote.dll
Microsoft.CLRAdmin
mscorcfg.dll
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Regular Expressions
Table 1-7 through Table 1-16 summarize the regular-expression grammar and syntax supported by the regular-expression classes in System.Text.RegularExpressions. (For more information, see the ".NET Framework Regular Expressions" topic in the .NET Framework SDK Documentation.) Each of the modifiers and qualifiers in the tables can substantially change the behavior of the matching and searching patterns. For further information on regular expressions, we recommend the definitive Mastering Regular Expressions by Jeffrey E. F. Friedl (O'Reilly, 2002).
All the syntax described in the tables should match the Perl5 syntax, with specific exceptions noted.
Table 1-7: Character escapes
Escape code sequence
Meaning
Hexadecimal equivalent
\a
Bell