By Peter Drayton, Ben Albahari, Ted Neward
Cover | Table of Contents | Colophon
http://genamics.com/developer/csharp_comparative.htm./* ... */ notation. See Chapter 4.)XslTransform class, there are no
breaking changes that would prevent a 1.0 application from working on
the 1.1 Framework.namespace FirstProgram {
using System;
class Example {
static void Main ( ) {
Console.WriteLine ("Hello world!");
}
}
}
Example that contains a method named
Main, which has a single statement that writes
Hello
world! to the console
window. C# recognizes this method as the default entry point of
execution, so that's where the program begins.Console class
encapsulates
standard input/output functionality, providing methods such as
WriteLine. To use types from
another
namespace,
use the using directive. Since the
Console class resides in the
System namespace, we write
using
System; similarly, types
from other namespaces could use our Example class
by using
FirstProgram.using System;
class Test {
static void Main ( ) {
...
}
}
namespace FirstProgram {
using System;
class Example {
static void Main ( ) {
Console.WriteLine ("Hello world!");
}
}
}
Example that contains a method named
Main, which has a single statement that writes
Hello
world! to the console
window. C# recognizes this method as the default entry point of
execution, so that's where the program begins.Console class
encapsulates
standard input/output functionality, providing methods such as
WriteLine. To use types from
another
namespace,
use the using directive. Since the
Console class resides in the
System namespace, we write
using
System; similarly, types
from other namespaces could use our Example class
by using
FirstProgram.using System;
class Test {
static void Main ( ) {
...
}
}
@ 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:Ko
n
@Ko
n
abstract |
as |
base |
bool |
break |
byte |
case |
catch |
char |
checked |
class |
const |
continue |
decimal |
default |
delegate |
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
}
}
y is
updated, while in our previous example, y remained
unchanged:using System;
using System.Text;
class Test {
static void Main ( ) {
StringBuilder x = new StringBuilder ("hello");
StringBuilder y = x;
x.Append (" there");
Console.WriteLine (y); // prints "hello there"
}
}
StringBuilder type is a
reference type, while the int type is a value
type. When we declared the StringBuilder variable,
we were actually doing two separate things, which can be separated
into these two lines:sbyte, short, int, long)byte, ushort, uint, ulong)float, decimal, double)System
namespace. For example, there is only a syntactic difference between
these two statements:int i = 5; System.Int32 i = 5;
|
C# type
|
System type
|
Size
|
Signed
|
|---|---|---|---|
sbyte
|
System.SByte
|
1 byte
|
yes
|
short
|
System.Int16
|
2 bytes
|
yes
|
int
|
type [*] + array-name = [ new type [ dimension+ ][*]*; | { value1, value2, ... }; ]
[*] is the set: [ ] [,] [,,]
...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.for loop, which is explained in the
statements section. The for loops here simply
iterate through each item in the arrays.// rectangular
int [,,] matrixR = new int [3, 4, 5]; // creates 1 big cube
// jagged
int [ ][ ][ ] matrixJ = new int [3][ ][ ];
for (int i = 0; i < 3; i++) {
matrixJ[i] = new int [4][ ];
for (int j = 0; j < 4; j++)
matrixJ[i][j] = new int [5];
}
// assign an element
matrixR [1,1,1] = matrixJ [1][1][1] = 7;
int[,] array = {{1,2},{3,4}};
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
}
}
|
Type
|
Default value
|
|---|---|
|
Numeric types
|
|
((1 + 2) / 3)
1 + 2 + 3 * 4
((1 + 2) + (3 * 4))
|
Category
|
Operators
|
|---|---|
|
Primary
|
Grouping:
(x)
Member access:
x.y
|
[variable =]? expr;
invocation, new,
++, --). An expression
statement ends in a semicolon. For example:x = 5 + 6; // assign result x++; // side effect y = Math.Min(x, 20); // side effect and assign result Math.Min(x, y); // discards result, but ok, there is a side effect x = = y; // error, has no side effect, and does not assign result
type [variable [ = expr ]?]+ ;
const type [variable = constant-expr]+;
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
}
const double speedOfLight = 2.99792458E08; speedOfLight+=10; // error
namespace name+ // Dot-delimited { using-statement* [namespace-declaration | type-declaration]* // No delimiters }
namespace MyCompany.MyProduct.Drawing {
class Point {int x, y, z;}
delegate void PointInvoker(Point p);
}
namespace MyCompany {
namespace MyProduct {
namespace Drawing {
class Point {int x, y, z;}
delegate void PointInvoker(Point p);
}
}
}
namespace TestProject {
class Test {
static void Main( ) {
MyCompany.MyProduct.Drawing.Point x;
}
}
}
attributes? unsafe? access-modifier?
new?
[ abstract | sealed ]?
class class-name
[: base-class |
: interface+ |
: base-class, interface+ ]?
{ class-members }
Planet,
Astronaut, and Test, to test
our simulation.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;}
}
}
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
attributes? unsafe? access-modifier?
new?
[ abstract | sealed ]?
class class-name
[: base-class |
: interface+ |
: base-class, interface+ ]?
{ class-members }
Planet,
Astronaut, and Test, to test
our simulation.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;}
}
}
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.Image in the System.Drawing
namespace, which the Bitmap,
Icon, and Metafile classes
inherit from. All classes are ultimately part of a single giant class
hierarchy, of which the root is the Object class.
All classes implicitly inherit from it.Location. This class is very basic, and provides a
location with a name property and a way to display itself to the
console window:class Location { // Implicitly inherits from object
string name;
// The constructor that initializes Location
public Location(string n) {
name = n;
}
public string Name {get {return name;}}
public void Display( ) {
System.Console.WriteLine(Name);
}
}
URL, which will
inherit from Location. The URL
class has all the same members as Location, as
well as a new member, Navigate. Inheriting from a
class requires specifying the class to inherit from the class
declaration, using the C++ colon notation:class URL : Location { // Inherit from Location
public void Navigate( ) {
System.Console.WriteLine("Navigating to "+Name);
}
// The constructor for URL, which calls Location's constructor
public URL(string name) : base(name) { }
}
URL, then invoke both the
Display method (which is defined in
Location) and the navigate method (which is
defined in URL):class Test {
static void Main( ) {
URL u = new URL("http://microsoft.com");
u.Display( );
u.Navigate( );
}
}
// 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
}
}
attributes? unsafe? access-modifier?
new?
struct struct-name [: interface+]?
{ struct-members }
System.ValueType). Both classes and
structs can implement interfaces.
attributes? unsafe? access-modifier?
new?
interface interface-name
[ : base-interface+ ]?
{ interface-members }
public interface IDelete {
void Delete( );
}
attributes? 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, and informs the
runtime that the values in the enum can be bit-combined, and should
be decoded accordingly in the debugger or when outputting text to the
console. For example:Console.WriteLine(walls.Format( )); // Displays "North|West" Console.WriteLine(walls); // Calls walls.ToString, displays "5"
System.Enum type also provides many useful
static methods for enums that allow one to determine the underlying
type of an enum, to check if a specific value is supported, to
initialize an enum from a string constant, to retrieve a list of the
valid values, and other common operations such as conversions. Here
is an example of the usage:using System;
public enum Toggle : byte { Off=0, On=1 }
class Test {
static void Main( ) {
Type t = Enum.GetUnderlyingType(typeof(Toggle));
Console.WriteLine(t); // Prints "Byte"
bool bDimmed = Enum.IsDefined(typeof(Toggle), "Dimmed");
Console.WriteLine(bDimmed); // Prints "False"
Toggle tog =(Toggle)Enum.Parse(typeof(Toggle), "On");
Console.WriteLine(Enum.Format(typeof(Toggle), tog, "D")); // Prints "1"
Console.WriteLine(tog); // Prints "On"
object[ ] oa = Enum.GetValues(typeof(Toggle));
foreach(Toggle toggle in oa) // Prints "On=1, Off=0"
Console.WriteLine("{0}={1}", toggle,Enum.Format(typeof(Toggle),
toggle, "D"));
}
}
attributes? unsafe? access-modifier?
new?
delegate
[ void | type ]
delegate-name (parameter-list);
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);
}
}
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.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");
}
}
attributes? unsafe? access-modifier?
new?
delegate
[ void | type ]
delegate-name (parameter-list);
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);
}
}
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.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");
}
}
-= operator:Test {
MethodInvoker m = null;
m += new MethodInvoker(Foo);
m -= new MethodInvoker(Foo);
// m is now null
}