By Ben Albahari, Peter Drayton, Brad Merrill
Cover | Table of Contents | Colophon
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.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.namespace FirstProgram {
using System;
class Test {
static void Main( ) {
Console.WriteLine("Welcome to C#!");
}
}
}
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.Main as a default entry point of execution.
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";
}
}
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";
}
}
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
}
}
v =
a is commented out.((1 + 2) / 3)
1 + 2 + 3 * 4
((1 + 2) +(3 * 4))
({ and
}), to form a statement
block. A
statement block can be used anywhere a single statement is valid.[
variable
=]?
expression
;
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
type
[
variable
[ =
expression
]?]+ ;
const
type
[
variable = constant
-
expression
]+ ;
namespace
name
+
{
[
namespace-declaration | type-declaration
]*
}
Dot-delimited.
No delimiters.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( );
}
}
URL u = new URL( ); Location l = u; // upcast u = (URL)l; // downcast
InvalidCastException
is thrown.as
operator allows a downcast to be made
that evaluates to null if the downcast fails:u = l as URL;
public
internal
A is
accessible only from within A. This is the default
accessibility for nonnested types, so may be omitted.private
T is accessible only from
within T. This is the default accessibility for
class and struct members, so it may be omitted.protected
C is accessible only from
within C or from within a class that derives from
C.protected
internal
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.// 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
}
}?
access-modifier
?
new?
[
abstract
|
sealed
]?
class
class-name
[
:
interface
+ |
:
base-class
,
interface
+
]?
{
class-members
}
?
access-modifier
?
new?
struct
struct-name
[
:
interface
+
]?
{
struct-members
}
?
access-modifier
?
new?
interface
interface-name
[ : base-interface
+
]?
{
interface-members
}
abstract class, which is an abstract class
consisting of only abstract members.public interface IDelete {
void Delete( );
}
type
[*]+
array-name
=
new
type
[
dimension
+ ][*]*;[*] is the set: [] [,] [,,]
...
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"
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).// 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;
int[,] array = {{1,2},{3,4}};
?
access-modifier
?
new?
enum
enum-name
[ :
integer
type
]?
{ [
attributes
?
enum-member-name
[ = value
]?
]
*
}
public enum Direction {North, East, West, South}
Direction walls = Direction.East;
[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!");
[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"
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);
}
}?
access-modifier
?
new?
delegate
[
void | type
]
(
parameter-list
);
delegate bool Filter(string s);
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);
}
}
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");
}
}delegate void MoveEventHandler(object source, MoveEventArgs e);
System.
EventArgs
and stores data about the event.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;
}
}
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;
}
}
}
}
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.try
statement-block
[catch (exception type value
?)?
statement-block
]+
|
finally
statement-block
|
[catch (exception type value
?)?
statement-block
]+
finally
statement-block
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.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( );
}
}
}
catch
clause specifies what exception
type (including derived types) to catch. An exception must be of type
[[
target
:]?
attribute-name
(
[
named-param
=
expression
]+ |+, [
named-param
=
expression
]
+
)?]
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.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.Foo class is
serializable using the Serializable attribute:[Serializable]
public class Foo {...}|
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. |
unsafe keyword to perform C++-style pointer
operations on memory. Here is an example that uses pointers with a
managed object:#define DEBUG
class MyClass {
int x;
void Foo( ) {
# if DEBUG
Console.WriteLine("Testing: x = {0}", x);
# endif
...
}
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
#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.|
Preprocessor Directive
|
Action
|
#define
symbol
|
Defines symbol
|
#undef
symbol
|
// and /*...*/:int x = 3; // this is a comment MyMethod( ); /* this is a comment that spans two lines */
///
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.// 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"); }
}