By Edd Dumbill, Niel Bornstein
Price: $24.95 USD
£17.50 GBP
Cover | Table of Contents | Colophon
http://www.mono-project.com/. Users of
RPM-based systems (
Red
Hat, SuSE, and Fedora Core) should download the
RPMs suitable
for their Linux distribution.mcs (the Mono C#
compiler) and mono (the Mono virtual machine).mcs. Try compiling the Hello World example
from Section 2.1 with mcs
Hello.cs. The result should be a file called
Hello.exe.Mono JIT compiler version 0.91, (C) 2002-2004 Novell, Inc
and Contributors. www.go-mono.com
TLS: NPTL
GC: Included Boehm (with typed GC)
SIGSEGV : altstack
Globalization: ICU
http://www.monodevelop.com. If
possible, use precompiled packages for installation, because the
number of prerequisites required to compile MonoDevelop is a little
daunting. Users of the Debian (unstable) or Gentoo Linux
distributions should be able to locate a
monodevelop package using
apt-get or emerge.http://www.vim.org/. Emacs users should
install Brad Merrill's C# mode, available from
http://www.cybercom.net/~zbrad/DotNet/Emacs/.# 01-tooling/04-integrate
MCS = mcs
ifdef DEBUG
MCSFLAGS = -debug
endif
.PHONY: clean all
all: SimpleMain.exe Main.exe FatMain.exe Library1.dll
# default way to make executables
%.exe: %.cs
$(MCS) $(MCSFLAGS) -target:exe -out:$@ $(filter %.cs,$^) \
$(foreach dl,$(filter %.dll,$^),$(addprefix -r:,$(dl)))
# default way to make libraries
%.dll: %.cs
$(MCS) $(MCSFLAGS) -target:library -out:$@ $(filter %.cs,$^) \
$(foreach dl,$(filter %.dll,$^),$(addprefix -r:,$(dl)))
# SimpleMain.exe is automatically built from its sole
# source file, SimpleMain.cs
# Main.exe has two source files and links with Library1.dll
Main.exe: Main.cs Sprockets.cs Library1.dll
# Library1.dll is automatically built from its sole
# source file, Library1.cs
# FatMain.exe has some resources, and links against Library1.dll
# define them in variables, let make do the walking
FATMAIN_RESOURCES = monkey.png readme.txt
FATMAIN_SOURCES = Main.cs Sprockets.cs Widgets.cs
FATMAIN_LIBS = Library1.dll
FatMain.exe: $(FATMAIN_SOURCES) $(FATMAIN_RESOURCES) $(FATMAIN_LIBS)
$(MCS) $(MCSFLAGS) -target:exe -out:$@ \
$(foreach res,$(FATMAIN_RESOURCES), \
$(addprefix -resource:,$(res))) \
$(foreach dl,$(FATMAIN_LIBS),$(addprefix -r:,$(dl))) \
$(FATMAIN_SOURCES)
clean:
rm -f *.exe *.dllhttp://www.go-mono.com/, the Mono
web site is the authoritative source for information on Mono. As well
as a host of documentation, the Mono web site publishes the latest
news about the project. The comprehensive list of frequently asked
questions is a good resource for answering questions technical and
commercial alike about Mono. The API documentation that ships with
Monodoc is also
available on the Web, by following the
"Documentation" link.http://msdn.microsoft.com, contains a wealth
of reference material and tutorials on using C#, and the APIs common
to both Mono and the Microsoft .NET framework.// 02-csharp/01-languagebasics
public class HelloWorld {
public static void Main (string [ ] args) {
if (args.Length != 1) {
System.Console.Error.WriteLine("You must tell me your name.");
System.Environment.Exit(-1);
}
string name = args[0];
System.Console.WriteLine ("Hello, {0}!", name);
}
}
$ mcs Hello.cs
// 02-csharp/01-languagebasics
public class HelloWorld {
public static void Main (string [ ] args) {
if (args.Length != 1) {
System.Console.Error.WriteLine("You must tell me your name.");
System.Environment.Exit(-1);
}
string name = args[0];
System.Console.WriteLine ("Hello, {0}!", name);
}
}
$ mcs Hello.cs
$ mono Hello.exe Niel
Hello, Niel!
HelloWorld is small; it only contains
the
Main( ) method. Any
class may have a Main( ) method; if more than
one class passed to mcs.exe does have a
Main( ) method, you can specify which one
contains the method for a given executable at compile time with
the
-main:classname
command-line switch.class and interface types. C#
adds array and delegate types.
In this lab you'll see how they work.Beverage class represents a beverage, about which
certain properties can be queried. Every beverage is assumed to have
a brand name, and every beverage is assumed to have a volume in fluid
ounces. It is also assumed to contain some amount of
caffeine-we're programmers, right?// 02-csharp/02-reftypes
using System;
public class Beverage {
private string brand;
private double volume;
private double percentCaffeine;
public Beverage(string brand, double volume,
double percentCaffeine) {
this.brand = brand;
this.volume = volume;
this.percentCaffeine = percentCaffeine;
}
public string Brand {
get {
return brand;
}
}
public double Volume {
get {
return volume;
}
}
public double PercentCaffeine {
get {
return percentCaffeine;
}
}
public override string ToString( ) {
return Volume + " oz " + Brand +
" with " + PercentCaffeine + "% caffeine";
}
}
public class BeverageTester {
public static void Main(string [ ] args) {
Beverage [ ] beverages = new Beverage [2];
Beverage jolt = new Beverage("Jolt", 12.0, 0.25);
beverages[0] = jolt;
Beverage coke = new Beverage("Coca-Cola", 12.0, 0.125);
beverages[1] = coke;
foreach (Beverage beverage in beverages) {
Console.WriteLine(beverage);
}
}
}struct and enum types.// 02-csharp/03-valuetypes
using System;
public enum Units {
Ounces,
Liters,
Pints
}
public struct BeverageSize {
private double volume;
private Units units;
public BeverageSize(double volume, Units units) {
this.volume = volume;
this.units = units;
}
public override string ToString( ) {
return volume + " " + units;
}
}
public class ValueTypesTester {
public static void Main(string [ ] args) {
BeverageSize size = new BeverageSize(12.0, Units.Ounces);
Console.WriteLine(size);
}
}
$ mcs ValueTypes.cs $ mono ValueTypes.exe 12 Ounces
enum and
struct keywords. All value types, like all other
CLI types, ultimately derive from System.Object,
but they have an additional intermediate base class,
System.ValueType. Value types differ from
reference types in that a variable of value type contains the actual
object data. Compare this to reference type variables, which, you
will remember, contain a reference to the location of the object
data. Additionally, value types are allocated on the stack, as
opposed to reference types, which are allocated on the heap. Value
types may not be null.enum.
enum declares an enumeration type that derives
from System.Enum. In addition to the C/C++-like
behavior you would expect from an enumeration type, the
System.Exception
class and the try...catch...finally syntax are
first-class members of the language, and are easy to understand and
use. You can catch exceptions thrown by class libraries, rethrow
them, clean up before moving on, and even declare and throw your own
custom exceptions.FileFinder which handles some errors that might
arise while opening a file.// 02-csharp/04-exceptions
using System;
using System.IO;
public class FileFinder {
public static void Main(string [ ] args) {
string filename = null;
try {
// will throw IndexOutOfRangeException if the
// filename is not specified
filename = args[0];
// will throw FileNotFoundException if the file
// does not exist
using(StreamReader reader = File.OpenText(filename)) {
string contents = reader.ReadToEnd( );
}
} catch (IndexOutOfRangeException e) {
Console.Error.WriteLine("No filename specified.");
Environment.Exit(1);
} catch (FileNotFoundException e) {
Console.Error.WriteLine("File \"{0}\" does not exist.",
filename);
Environment.Exit(2);
} catch (Exception e) {
Console.Error.WriteLine(e);
Environment.Exit(3);
} finally {
Console.WriteLine("Done");
}
}
}
$ mcs FileFinder.cs
IndexOutOfRangeException
and print an error message:WatchDirectory demonstrates the general concepts
of delegates and events, as well as giving a preview of one of the
classes in the System.IO namespace,
FileSystemWatcher.// 02-csharp/05-delegates
using System;
using System.IO;
public class WatchDirectory {
private static void OnFileSystemEvent (object sender,
FileSystemEventArgs e) {
Console.WriteLine("Something happened to {0}", e.Name);
}
private static void OnChanged (object sender,
FileSystemEventArgs e) {
Console.WriteLine("{0} changed", e.Name);
}
private static void OnCreated (object sender,
FileSystemEventArgs e) {
Console.WriteLine("{0} created", e.Name);
}
private static void OnDeleted (object sender,
FileSystemEventArgs e) {
Console.WriteLine("{0} deleted", e.Name);
}
private static void OnRenamed (object sender,
RenamedEventArgs e) {
Console.WriteLine("{0} renamed to {1}", e.OldName, e.Name);
}
public static void Main(string [ ] args) {
string path = (string)args[0];
FileSystemWatcher watcher = new FileSystemWatcher(path);
watcher.Filter = "*";
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.Created += new FileSystemEventHandler(OnCreated);
watcher.Deleted += new FileSystemEventHandler(OnDeleted);
watcher.Renamed += new RenamedEventHandler(OnRenamed);
FileSystemEventHandler onFileSystemEvent =
new FileSystemEventHandler(OnFileSystemEvent);
watcher.Changed += onFileSystemEvent;
watcher.Created += onFileSystemEvent;
watcher.Deleted += onFileSystemEvent;
watcher.EnableRaisingEvents = true;
Console.WriteLine("Enabled watcher on {0}; " +
"hit return to terminate.", path);
Console.ReadLine( );
watcher.EnableRaisingEvents = false;
watcher.Changed -= new FileSystemEventHandler(OnChanged);
watcher.Created -= new FileSystemEventHandler(OnCreated);
watcher.Deleted -= new FileSystemEventHandler(OnDeleted);
watcher.Renamed -= new RenamedEventHandler(OnRenamed);
watcher.Changed -= onFileSystemEvent;
watcher.Created -= onFileSystemEvent;
watcher.Deleted -= onFileSystemEvent;
Console.WriteLine("done");
}
}System.Attribute that may be placed
on any C# language feature. Attributes add information about, and
functionality to, the language features to which they are applied.System.SerializableAttribute
is one attribute that shows up quite often; it's
used to mark a class which can be serialized, which is necessary for
remoting and XML serialization (see Section 6.7).
Example 2-10 shows a class which uses the
SerializableAttribute attribute.// 02-csharp/06-attributes
using System;
[Serializable]
public class SerializableClass {
// ...
}
SerializableAttribute is a perfect example; a
class that is marked with the Serializable
attribute does not implement any special methods or add any
particular fields or properties. Instead, the
System.Xml.Serialization.XmlSerializerCurses, is a very rudimentary wrapper
around the ncurses terminal-based user interface
library.// 02-csharp/07-pinvoke
using System;
using System.Runtime.InteropServices;
public class Curses {
const string Library = "ncurses";
[DllImport(Library)]
private extern static IntPtr initscr( );
[DllImport(Library)]
private extern static int endwin( );
[DllImport(Library)]
private extern static int mvwprintw(IntPtr window,
int y, int x, string message);
[DllImport(Library)]
private extern static int refresh(IntPtr window);
[DllImport(Library)]
private extern static int wgetch(IntPtr window);
private IntPtr window;
public Curses( ) {
window = initscr( );
}
~Curses( ) {
int result = endwin( );
}
public int Print(int x, int y, string message) {
return mvwprintw(window, y, x, message);
}
public int Refresh( ) {
return refresh(window);
}
public char GetChar( ) {
return (char)wgetch(window);
}
}
public class HelloCurses {
public static void Main(string [ ] args) {
Curses Curses = new Curses( );
Curses.Print(10, 10, "Hello, curses!");
Curses.Refresh( );
char c = Curses.GetChar( );
Curses = null;
}
}-target:library
command-line argument to mcs. However,
you've actually been creating an assembly every time
you compile any Mono executable. A Mono library is simply an assembly
without a Main( ) method, while a Mono
executable is an assembly with a Main( ) entry
point.File,
TextReader, and TextWriter.
Example 3-1 shows the source for a class that uses
these three classes to create a file, write to it, read from it, and
set some of the file's attributes.// 03-keyfunc/01-files
using System;
using System.IO;
public class FileCreator {
public static void Main(string [ ] args) {
string file = args[0];
if (File.Exists(file)) {
Console.WriteLine("File {0} exists with attributes {1}," +
"created at {2}", file, File.GetAttributes(file),
File.GetCreationTime(file));
} else {
using (TextWriter writer = File.CreateText(file)) {
writer.WriteLine("Greetings from Mono!");
}
File.SetAttributes(file,
File.GetAttributes(file) | FileAttributes.ReadOnly |
FileAttributes.Temporary);
}
using (TextReader reader = File.OpenText(file)) {
Console.WriteLine(reader.ReadToEnd( ));
}
}
}File,
TextReader, and TextWriter.
Example 3-1 shows the source for a class that uses
these three classes to create a file, write to it, read from it, and
set some of the file's attributes.// 03-keyfunc/01-files
using System;
using System.IO;
public class FileCreator {
public static void Main(string [ ] args) {
string file = args[0];
if (File.Exists(file)) {
Console.WriteLine("File {0} exists with attributes {1}," +
"created at {2}", file, File.GetAttributes(file),
File.GetCreationTime(file));
} else {
using (TextWriter writer = File.CreateText(file)) {
writer.WriteLine("Greetings from Mono!");
}
File.SetAttributes(file,
File.GetAttributes(file) | FileAttributes.ReadOnly |
FileAttributes.Temporary);
}
using (TextReader reader = File.OpenText(file)) {
Console.WriteLine(reader.ReadToEnd( ));
}
}
}
TextReader and TextWriter both
implement IDisposable, so you can wrap them in the
string class.// 03-keyfunc/02-strings
using System;
public class Strings {
public static void Main(string [ ] args) {
string s1 = "a string!";
string s2 = @"a string with \escaped characters,
including a carriage return";
string s3 = new String('a', 4);
string s4 = s3 + s1;
string s5 = s4.Replace('a', 'b');
Console.WriteLine(s1);
Console.WriteLine(s2);
Console.WriteLine(s3);
Console.WriteLine(s4);
Console.WriteLine(s5);
}
}
$ mcs Strings.cs $ mono Strings.exe a string! a string with \escaped characters, including a carriage return aaaa aaaaa string! bbbbb string!
s1, is assigned to the
literal value "a string!". Simple
enough.s2, is prefixed with the
@ character. This
@-quoted (literal) string contains an embedded backslash and a
carriage return.
Special characters such as these
typically need to be escaped using the \ and
\n character sequences. By @-quoting the string,
any characters that normally need to be escaped can be put in the
string literally.// 03-keyfunc/03-regex
using System;
using System.Collections;
using System.IO;
using System.Text.RegularExpressions;
public class ParseHosts {
public static void Main(string [ ] args) {
string filename;
if (Environment.OSVersion.ToString( ).StartsWith("Unix")) {
filename = string.Format("{0}etc{0}hosts",
Path.DirectorySeparatorChar);
} else {
filename = string.Format("{0}{1}drivers{1}etc{1}hosts",
Environment.GetFolderPath(Environment.SpecialFolder.System),
Path.DirectorySeparatorChar);
}
if (!File.Exists(filename)) {
Console.Error.WriteLine("{0} does not exist.", filename);
Environment.Exit(1);
}
string text;
using (TextReader reader = File.OpenText(filename)) {
text = reader.ReadToEnd( );
}
Regex regex =
new Regex(@"(?<ip>(\d{1,3}\.){3}\d{1,3})\s+(?<name>(\S+))");
MatchCollection matches = regex.Matches(text);
foreach (Match match in matches) {
if (match.Length != 0) {
Console.WriteLine("hostname {0} is mapped to ip address {1}",
match.Groups["name"], match.Groups["ip"]);
}
}
}
}// 03-keyfunc/04-collections
using System;
using System.Collections;
public class PrintEnvironment {
public static void Main(string [ ] args) {
IDictionary variables = Environment.GetEnvironmentVariables( );
ICollection variableNames = variables.Keys;
foreach (string variableName in variableNames) {
Console.WriteLine("{0}={1}", variableName, variables[variableName]);
}
}
}
$ mcs PrintEnvironment.cs $ mono PrintEnvironment.exe MANPATH=/sw/share/man:/usr/share/man:/usr/local/man:/usr/X11R6/man VISUAL=/usr/bin/vi TERM_PROGRAM=Apple_Terminal TERM_PROGRAM_VERSION=100 OLDPWD=/Users/niel _ _CF_USER_TEXT_ENCODING=0x1F5:0:0 XML_CATALOG_FILES=/sw/etc/xml/catalog INFOPATH=/sw/share/info:/sw/info:/usr/share/info LOGNAME=niel TERM=xterm-color USER=niel PERL5LIB=/sw/lib/perl5:/sw/lib/perl5 _=/usr/local/bin/mono SGML_CATALOG_FILES=/sw/etc/sgml/catalog PWD=/Users/niel/Documents/Mono Developers Notebook/monodn SHELL=/bin/bash SECURITYSESSIONID=210970 HOME=/Users/niel PATH=/sw/bin:/sw/sbin:/bin:/sbin:/usr/bin:/usr/local/bin:/usr/sbin:/usr/X11R6/bin: /usr/local/bin SHLVL=1
Sprocket and Widget, along with
attributes from AssemblyInfo.cs and resources
from SprocketSet.resources. The contents of
Sprocket.cs are:public class Sprocket {
}
public class Widget {
public Sprocket Sprocket;
}
.assembly extern mscorlib
{
.ver 1:0:5000:0
}
.assembly 'SprocketSet'
{
.custom instance void class [mscorlib]'System.Reflection.AssemblyKeyNameAttribute'
::.ctor (string) = (01 00 00 00 00 ) // .....
.custom instance void class [mscorlib]'System.Security.
AllowPartiallyTrustedCallersAttribute'::.ctor( ) = (01 00 00 00 ) // ....
.custom instance void class [mscorlib]'System.Reflection.AssemblyTitleAttribute'::.ctor
(string) = (
01 00 0B 53 70 72 6F 63 6B 65 74 53 65 74 00 00 ) // ...SprocketSet..
.custom instance void class [mscorlib]'System.Reflection.AssemblyDescriptionAttribute'
::.ctor(string) = (
01 00 21 42 75 69 6C 64 20 53 70 72 6F 63 6B 65 // ..!Build Sprocke
74 73 20 66 6F 72 20 79 6F 75 72 20 77 69 64 67 // ts for your widg
65 74 73 2E 00 00 ) // ets...
.custom instance void class [mscorlib]'System.Reflection.AssemblyConfigurationAttribute'
::.ctor(string) = (01 00 07 52 65 6C 65 61 73 65 00 00 ) // ...Release..
.custom instance void class [mscorlib]'System.Reflection.AssemblyCompanyAttribute'
::.ctor(string) = (
01 00 13 41 6D 61 6C 67 61 6D 61 74 65 64 20 57 // ...Amalgamated W
69 64 67 65 74 73 00 00 ) // idgets..
.custom instance void class [mscorlib]'System.Reflection.AssemblyProductAttribute'
::.ctor(string) = (
01 00 0B 53 70 72 6F 63 6B 65 74 53 65 74 00 00 ) // ...SprocketSet..
.custom instance void class [mscorlib]'System.Reflection.AssemblyCopyrightAttribute'
::.ctor(string) = (
01 00 2E 32 30 30 34 20 41 6D 61 6C 67 61 6D 61 // ...2004 Amalgama
74 65 64 20 57 69 64 67 65 74 73 2E 20 41 6C 6C // ted Widgets. All
20 52 69 67 68 74 73 20 52 65 73 65 72 76 65 64 // Rights Reserved
2E 00 00 ) // ...
.custom instance void class [mscorlib]'System.Reflection.AssemblyTrademarkAttribute'
::.ctor(string) = (01 00 00 00 00 ) // .....
.custom instance void class [mscorlib]'System.Reflection.AssemblyCultureAttribute'
::.ctor(string) = (01 00 05 65 6E 2D 55 53 00 00 ) // ...en-US..
.custom instance void class [mscorlib]'System.Reflection.AssemblyDelaySignAttribute'
::.ctor(bool) = (01 00 00 00 00 ) // .....
.custom instance void class [mscorlib]'System.Reflection.AssemblyKeyFileAttribute'
::.ctor(string) = (
01 00 0F 53 70 72 6F 63 6B 65 74 53 65 74 2E 73 // ...SprocketSet.s
6E 6B 00 00 ) // nk..
.custom instance void class [mscorlib]'System.CLSCompliantAttribute'::.ctor(bool) =
(01 00 01 00 00 ) // .....
.hash algorithm 0x00008004
.ver 1:0:1601:37419
.locale en-US
.publickey = (
00 24 00 00 04 80 00 00 94 00 00 00 06 02 00 00 // .$..............
00 24 00 00 52 53 41 31 00 04 00 00 11 00 00 00 // .$..RSA1........
C3 9F ED 12 DB 31 A8 97 5A 3C D6 01 3F E6 09 22 // .....1..Z<..?.."
76 F8 60 A0 A2 6D 49 DD 81 1C E5 12 FB 92 36 94 // v.`..mI.......6.
93 D8 EC 6D F8 3F D1 FC 4A 02 21 B7 6F 06 BE 18 // ...m.?..J.!.o...
56 F0 7C 6B C0 D1 07 1A A8 8F 1E FD 38 5E A6 20 // V.|k........8^.
FD 36 86 E0 12 BE 91 89 DB C0 C2 D6 4F 5B FD 76 // .6..........O[.v
E1 47 16 8D 67 A0 E6 00 9E B3 A1 5B 75 09 8C 75 // .G..g......[u..u
36 07 E8 31 E4 8B F2 6D 3B 12 28 0E 1C CC 75 45 // 6..1...m;.(...uE
55 3B FD 55 BC 60 8E 7C 93 01 78 C6 3A 77 5E C6 ) // U;.U.`.|..x.:w^.
}
.module 'SprocketSet.dll' // GUID = {F7829D2A-5DAC-5B46-AD1D-D6CD2F7899CE}
.class public auto ansi beforefieldinit 'Sprocket'
extends [mscorlib]System.Object
{
// method line 1
.method public hidebysig specialname rtspecialname
instance default void .ctor ( ) cil managed
{
// Method begins at RVA 0x20ec
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void valuetype [mscorlib]'System.Object'::.ctor( )
IL_0006: ret
} // end of method Sprocket::instance default void .ctor ( )
} // end of type Sprocket
.class public auto ansi beforefieldinit 'Widget'
extends [mscorlib]System.Object
{
.field public class 'Sprocket' 'Sprocket'
// method line 2
.method public hidebysig specialname rtspecialname
instance default void .ctor ( ) cil managed
{
// Method begins at RVA 0x20f4
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void valuetype [mscorlib]'System.Object'::.ctor( )
IL_0006: ret
} // end of method Widget::instance default void .ctor ( )
} // end of type Widgetsleep command, taking the number of seconds to
sleep from a command-line argument.// 03-keyfunc/06-process
using System;
using System.Diagnostics;
public class StartProcess {
public static void Main(string [ ] args) {
string sleepTime = args[0];
ProcessStartInfo startInfo = new ProcessStartInfo( );
startInfo.FileName = "sleep";
startInfo.Arguments = sleepTime;
Console.WriteLine("Starting {0} {1} at {2}",
startInfo.FileName, startInfo.Arguments, DateTime.Now);
Process process = Process.Start(startInfo);
process.WaitForExit( );
Console.WriteLine("Done at {0}", DateTime.Now);
}
}
$ mcs StartProcess.cs $ mono StartProcess.exe 10 Starting sleep 10 at Wednesday, 12 May 2004 21:24:47 Done at Wednesday, 12 May 2004 21:24:58
sleep 10 was called, and that the
start and end times were printed to standard output.Process class. This program,
ListProcesses, lists all processes running on the
machine, with their process ID, name, and start time.using System;
using System.Diagnostics;
public class ListProcesses {
public static void Main(string [ ] args) {
foreach (Process process in Process.GetProcesses( )) {
Console.WriteLine("Process {0}: {1} on {2} started at {3}",
process.Id, process.ProcessName, process.MachineName,
process.StartTime);
}
}
}