Sie sind auf Seite 1von 152

MORE TUTORIALS

OVERALL TUTORIAL TOPICS

Tutorial Description Level


Hello World Shows a Hello World application. Simple
Command Line Shows simple command-line processing; also shows array Simple
Parameters indexing.
Arrays Shows how to use arrays. Simple
Properties Shows how properties are declared and used; also Simple
demonstrates abstract properties.
Libraries Shows how to use compiler options to create a DLL library Simple
from multiple source files; also, how to use them in other
programs.
Versioning Shows the use of override and new to support versioning. Simple
Collection Classes Shows how to make collection classes that can be used Intermediate
with foreach.
Structs Shows how to use structs in C#. Intermediate
Indexers Shows how to use array notation to access an object. Intermediate
Indexed Properties Shows how to implement a class that uses indexed Intermediate
properties. Indexed properties allow you to use a class that
represents an array-like collection of several different kinds
of things.
User-Defined Shows how to define conversions to and from user-defined Intermediate
Conversions types.
Operator Overloading Shows how user-defined classes can overload operators. Intermediate
Delegates Shows how delegates are declared, mapped, and Intermediate
combined.
Events Shows how to use events in C#. Intermediate
Explicit Interface Demonstrates how to explicitly implement interface Intermediate
Implementation members.
Conditional Methods Demonstrates conditional methods, which provide a Intermediate
powerful mechanism by which calls to methods can be
included or omitted depending on whether a symbol is
defined.
XML Documentation Shows how to document code by using XML. Intermediate
Platform Invoke Shows how to call platform invokes (exported DLL Advanced
functions) from C#.
COM Interop Part 1 Shows how to use C# to interoperate with COM objects. Advanced
Covers a C# client.

1
COM Interop Part 2 Shows how to use C# to interoperate with COM objects. Advanced
Covers a C# server.
Attributes Shows how to create custom attribute classes, use them in Advanced
code, and query them through reflection.
Security Discusses .NET Framework security and shows two ways Advanced
to modify security permissions in C#: permission classes
and permission attributes.
Threading Demonstrates various thread activities such as creating and Advanced
executing a thread, synchronizing threads, interacting
between threads, using a thread pool, and using a mutex
object.
Unsafe Code Shows how to use pointers. Advanced
OLE DB Shows how to use OLE DB in C# by connecting to a Advanced
Microsoft Access database.

Hello World Tutorial


The following examples show different ways of writing the C# Hello World program.

Example 1

// Hello1.cs
public class Hello1
{
public static void Main()
{
System.Console.WriteLine("Hello, World!");
}
}

Output

Hello, World!

Code Discussion

• Every Main method must be contained inside a class (Hello1 in this case).

• The System.Console class contains a WriteLine method that can be used to display a
string to the console.

2
Example 2

To avoid fully qualifying classes throughout a program, you can use the using directive as shown
below:

// Hello2.cs
using System;

public class Hello2


{
public static void Main()
{
Console.WriteLine("Hello, World!");
}
}

Output

Hello, World!

Example 3

If you need access to the command line parameters passed in to your application, simply change
the signature of the Main method to include them as shown below. This example counts and
displays the command line arguments.

// Hello3.cs
// arguments: A B C D
using System;

public class Hello3


{
public static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
Console.WriteLine("You entered the following {0} command line arguments:",
args.Length );
for (int i=0; i < args.Length; i++)
{
Console.WriteLine("{0}", args[i]);
}
}

3
}

Output

Hello, World!
You entered the following 4 command line arguments:
A
B
C
D

Example 4

To return a return code, change the signature of the Main method as shown below:

// Hello4.cs
using System;

public class Hello4


{
public static int Main(string[] args)
{
Console.WriteLine("Hello, World!");
return 0;
}
}

Output

Hello, World!

Command Line Parameters Tutorial

This tutorial shows how the command line can be accessed and two ways of accessing the array
of command line parameters.

The following examples show two different approaches to using the command line arguments
passed to an application.

Example 1

This example demonstrates how to print out the command line arguments.

4
// cmdline1.cs
// arguments: A B C
using System;

public class CommandLine


{
public static void Main(string[] args)
{
// The Length property is used to obtain the length of the array.
// Notice that Length is a read-only property:
Console.WriteLine("Number of command line parameters = {0}",
args.Length);
for(int i = 0; i < args.Length; i++)
{
Console.WriteLine("Arg[{0}] = [{1}]", i, args[i]);
}
}
}

Output

Run the program using some arguments like this: cmdline1 A B C.

The output will be:

Number of command line parameters = 3


Arg[0] = [A]
Arg[1] = [B]
Arg[2] = [C]

Example 2

Another approach to iterating over the array is to use the foreach statement as shown in this
example. The foreach statement can be used to iterate over an array or over a .NET Framework
collection class. It provides a simple way to iterate over collections.

// cmdline2.cs
// arguments: John Paul Mary
using System;

public class CommandLine2


{

5
public static void Main(string[] args)
{
Console.WriteLine("Number of command line parameters = {0}",
args.Length);
foreach(string s in args)
{
Console.WriteLine(s);
}
}
}

Output

Run the program using some arguments like this: cmdline2 John Paul Mary.

The output will be:

Number of command line parameters = 3


John
Paul
Mary

Arrays Tutorial

This tutorial describes arrays and shows how they work in C#.

This tutorial is divided into the following sections:

• Arrays in General

• Declaring Arrays

• Initializing Arrays

• Accessing Array Members

• Arrays are Objects

• Using foreach with Arrays

Arrays in General
C# arrays are zero indexed; that is, the array indexes start at zero. Arrays in C# work similarly to
how arrays work in most other popular languages There are, however, a few differences that you
should be aware of.

6
When declaring an array, the square brackets ([]) must come after the type, not the identifier.
Placing the brackets after the identifier is not legal syntax in C#.

int[] table; // not int table[];


Another detail is that the size of the array is not part of its type as it is in the C language. This
allows you to declare an array and assign any array of int objects to it, regardless of the array's
length.

int[] numbers; // declare numbers as an int array of any size


numbers = new int[10]; // numbers is a 10-element array
numbers = new int[20]; // now it's a 20-element array

Declaring Arrays
C# supports single-dimensional arrays, multidimensional arrays (rectangular arrays), and array-
of-arrays (jagged arrays). The following examples show how to declare different kinds of arrays:

Single-dimensional arrays:

int[] numbers;
Multidimensional arrays:

string[,] names;
Array-of-arrays (jagged):

byte[][] scores;
Declaring them (as shown above) does not actually create the arrays. In C#, arrays are objects
(discussed later in this tutorial) and must be instantiated. The following examples show how to
create arrays:

Single-dimensional arrays:

int[] numbers = new int[5];

Multidimensional arrays:

string[,] names = new string[5,4];


Array-of-arrays (jagged):

byte[][] scores = new byte[5][];


for (int x = 0; x < scores.Length; x++)
{
scores[x] = new byte[4];
}

7
You can also have larger arrays. For example, you can have a three-dimensional rectangular
array:

int[,,] buttons = new int[4,5,3];


You can even mix rectangular and jagged arrays. For example, the following code declares a
single-dimensional array of three-dimensional arrays of two-dimensional arrays of type int:

int[][,,][,] numbers;

Example

The following is a complete C# program that declares and instantiates arrays as discussed above.

// arrays.cs
using System;
class DeclareArraysSample
{
public static void Main()
{
// Single-dimensional array
int[] numbers = new int[5];

// Multidimensional array
string[,] names = new string[5,4];

// Array-of-arrays (jagged array)


byte[][] scores = new byte[5][];

// Create the jagged array


for (int i = 0; i < scores.Length; i++)
{
scores[i] = new byte[i+3];
}

// Print length of each row


for (int i = 0; i < scores.Length; i++)
{
Console.WriteLine("Length of row {0} is {1}", i, scores[i].Length);
}
}
}

8
Output

Length of row 0 is 3
Length of row 1 is 4
Length of row 2 is 5
Length of row 3 is 6
Length of row 4 is 7

Initializing Arrays
C# provides simple and straightforward ways to initialize arrays at declaration time by enclosing
the initial values in curly braces ({}). The following examples show different ways to initialize
different kinds of arrays.

Note If you do not initialize an array at the time of declaration, the array members are
automatically initialized to the default initial value for the array type. Also, if you declare the array
as a field of a type, it will be set to the default value null when you instantiate the type.

Single-Dimensional Array

int[] numbers = new int[5] {1, 2, 3, 4, 5};


string[] names = new string[3] {"Matt", "Joanne", "Robert"};
You can omit the size of the array, like this:

int[] numbers = new int[] {1, 2, 3, 4, 5};


string[] names = new string[] {"Matt", "Joanne", "Robert"};
You can also omit the new operator if an initializer is provided, like this:

int[] numbers = {1, 2, 3, 4, 5};


string[] names = {"Matt", "Joanne", "Robert"};

Multidimensional Array

int[,] numbers = new int[3, 2] { {1, 2}, {3, 4}, {5, 6} };


string[,] siblings = new string[2, 2] { {"Mike","Amy"}, {"Mary","Albert"} };
You can omit the size of the array, like this:

int[,] numbers = new int[,] { {1, 2}, {3, 4}, {5, 6} };


string[,] siblings = new string[,] { {"Mike","Amy"}, {"Mary","Albert"} };
You can also omit the new operator if an initializer is provided, like this:

int[,] numbers = { {1, 2}, {3, 4}, {5, 6} };


string[,] siblings = { {"Mike", "Amy"}, {"Mary", "Albert"} };

Jagged Array (Array-of-Arrays)

9
You can initialize jagged arrays like this example:

int[][] numbers = new int[2][] { new int[] {2,3,4}, new int[] {5,6,7,8,9} };
You can also omit the size of the first array, like this:

int[][] numbers = new int[][] { new int[] {2,3,4}, new int[] {5,6,7,8,9} };
-or-

int[][] numbers = { new int[] {2,3,4}, new int[] {5,6,7,8,9} };


Notice that there is no initialization syntax for the elements of a jagged array.

Accessing Array Members


Accessing array members is straightforward and similar to how you access array members in
C/C++. For example, the following code creates an array called numbers and then assigns a 5 to
the fifth element of the array:

int[] numbers = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0};


numbers[4] = 5;
The following code declares a multidimensional array and assigns 5 to the member located at [1,
1]:

int[,] numbers = { {1, 2}, {3, 4}, {5, 6}, {7, 8}, {9, 10} };
numbers[1, 1] = 5;
The following is a declaration of a single-dimension jagged array that contains two elements. The
first element is an array of two integers, and the second is an array of three integers:

int[][] numbers = new int[][] { new int[] {1, 2}, new int[] {3, 4, 5}
};
The following statements assign 58 to the first element of the first array and 667 to the second
element of the second array:

numbers[0][0] = 58;
numbers[1][1] = 667;

Arrays are Objects


In C#, arrays are actually objects. System.Array is the abstract base type of all array types. You
can use the properties, and other class members, that System.Array has. An example of this
would be using the Length property to get the length of an array. The following code assigns the
length of the numbers array, which is 5, to a variable called LengthOfNumbers:

int[] numbers = {1, 2, 3, 4, 5};

10
int LengthOfNumbers = numbers.Length;
The System.Array class provides many other useful methods/properties, such as methods for
sorting, searching, and copying arrays.

Using foreach on Arrays


C# also provides the foreach statement. This statement provides a simple, clean way to iterate
through the elements of an array. For example, the following code creates an array called
numbers and iterates through it with the foreach statement:

int[] numbers = {4, 5, 6, 1, 2, 3, -2, -1, 0};


foreach (int i in numbers)
{
System.Console.WriteLine(i);
}
With multidimensional arrays, you can use the same method to iterate through the elements, for
example:

int[,] numbers = new int[3, 2] {{9, 99}, {3, 33}, {5, 55}};
foreach(int i in numbers)
{
Console.Write("{0} ", i);
}
The output of this example is:

9 99 3 33 5 55
However, with multidimensional arrays, using a nested for loop gives you more control over the
array elements.

Properties Tutorial

This tutorial shows how properties are an integral part of the C# programming language. It
demonstrates how properties are declared and used.

This tutorial includes two examples. The first example shows how to declare and use read/write
properties. The second example demonstrates abstract properties and shows how to override
these properties in subclasses.

Example 1

This sample shows a Person class that has two properties: Name (string) and Age (int). Both
properties are read/write.

// person.cs

11
using System;
class Person
{
private string myName ="N/A";
private int myAge = 0;

// Declare a Name property of type string:


public string Name
{
get
{
return myName;
}
set
{
myName = value;
}
}

// Declare an Age property of type int:


public int Age
{
get
{
return myAge;
}
set
{
myAge = value;
}
}

public override string ToString()


{
return "Name = " + Name + ", Age = " + Age;
}

public static void Main()


{
Console.WriteLine("Simple Properties");

12
// Create a new Person object:
Person person = new Person();

// Print out the name and the age associated with the person:
Console.WriteLine("Person details - {0}", person);

// Set some values on the person object:


person.Name = "Joe";
person.Age = 99;
Console.WriteLine("Person details - {0}", person);

// Increment the Age property:


person.Age += 1;
Console.WriteLine("Person details - {0}", person);
}
}

Output

Simple Properties
Person details - Name = N/A, Age = 0
Person details - Name = Joe, Age = 99
Person details - Name = Joe, Age = 100

Code Discussion

• Notice the way that the properties are declared, for example, consider the Name property:

public string Name


{
get
{
return myName;
}
set
{
myName = value;
}
}

13
The Set and Get methods of a property are contained inside the property declaration. You
can control whether a property is read/write, read-only, or write-only by controlling
whether a Get or Set method is included.

• Once the properties are declared, they can be used as if they were fields of the class.
This allows for a very natural syntax when both getting and setting the value of a
property, as in the following statements:

person.Name = "Joe";
person.Age = 99;

• Note that in a property Set method a special value variable is available. This variable
contains the value that the user specified, for example:

myName = value;

• Notice the clean syntax for incrementing the Age property on a Person object:

person.Age += 1;
If separate Set and Get methods were used to model properties, the equivalent code
might look like this:

person.SetAge(person.GetAge() + 1);

• The ToString method is overridden in this example:

public override string ToString()


{
return "Name = " + Name + ", Age = " + Age;
}
Notice that ToString is not explicitly used in the program. It is invoked by default by the
WriteLine calls.

Example 2

The following example shows how to define abstract properties. An abstract property declaration
does not provide an implementation of the property accessors. The example demonstrates how to
override these properties in subclasses.

14
This sample consists of three files. In the Properties Sample, these files are compiled into a single
compilation but in this tutorial, each file is compiled individually and its resulting assembly
referenced by the next compilation:

• abstractshape.cs: The Shape class that contains an abstract Area property.

• shapes.cs: The subclasses of the Shape class.

• shapetest.cs: A test program to display the areas of some Shape-derived objects.

To compile the example, use the command line:

csc abstractshape.cs shapes.cs shapetest.cs


This will create the executable file shapetest.exe.

File 1 - abstractshape.cs

This file declares the Shape class that contains the Area property of the type double

// abstractshape.cs
// compile with: /target:library
// csc /target:library abstractshape.cs
using System;

public abstract class Shape


{
private string myId;

public Shape(string s)
{
Id = s; // calling the set accessor of the Id property
}

public string Id
{
get
{
return myId;
}

set
{
myId = value;
}

15
}

// Area is a read-only property - only a get accessor is needed:


public abstract double Area
{
get;
}

public override string ToString()


{
return Id + " Area = " + string.Format("{0:F2}",Area);
}
}

Code Discussion

• Modifiers on the property are placed on the property declaration itself, for example:

public abstract double Area

• When declaring an abstract property (such as Area in this example), you simply indicate
what property accessors are available, but do not implement them. In this example, only a
Get accessor is available, so the property is read-only.

File 2 - shapes.cs

The following code shows three subclasses of Shape and how they override the Area property to
provide their own implementation.

// shapes.cs
// compile with: /target:library /reference:abstractshape.dll
public class Square : Shape
{
private int mySide;

public Square(int side, string id) : base(id)


{
mySide = side;
}

16
public override double Area
{
get
{
// Given the side, return the area of a square:
return mySide * mySide;
}
}
}

public class Circle : Shape


{
private int myRadius;

public Circle(int radius, string id) : base(id)


{
myRadius = radius;
}

public override double Area


{
get
{
// Given the radius, return the area of a circle:
return myRadius * myRadius * System.Math.PI;
}
}
}

public class Rectangle : Shape


{
private int myWidth;
private int myHeight;

public Rectangle(int width, int height, string id) : base(id)


{
myWidth = width;
myHeight = height;
}

17
public override double Area
{
get
{
// Given the width and height, return the area of a rectangle:
return myWidth * myHeight;
}
}
}

File 3 - shapetest.cs

The following code shows a test program that creates a number of Shape-derived objects and
prints out their areas.

// shapetest.cs
// compile with: /reference:abstractshape.dll;shapes.dll
public class TestClass
{
public static void Main()
{
Shape[] shapes =
{
new Square(5, "Square #1"),
new Circle(3, "Circle #1"),
new Rectangle( 4, 5, "Rectangle #1")
};

System.Console.WriteLine("Shapes Collection");
foreach(Shape s in shapes)
{
System.Console.WriteLine(s);
}

}
}

Output

Shapes Collection
Square #1 Area = 25.00

18
Circle #1 Area = 28.27
Rectangle #1 Area = 20.00

Libraries Tutorial

This tutorial shows how to create and use libraries in C#.

This tutorial demonstrates how to create a managed DLL file by using the necessary compiler
options, and how to use the library by a client program.

Example

This example uses the following modules:

• The DLL library (Functions.dll), which is built from the following source files:

Factorial.cs: Calculates and returns the factorial of a number.

DigitCounter.cs: Counts the number of digits in the passed string.

• The client program (FunctionTest.exe), which uses the DLL, is compiled from the source
file FunctionClient.cs. The program displays the factorial of the input arguments.

Building the Library

To build the library, make Functions the current directory and type the following at the command
prompt:

csc /target:library /out:Functions.dll Factorial.cs DigitCounter.cs


where:

/target:library Specifies that the output is a DLL and not an executable file (this also
stops the compiler from looking for a default entry point).
/out:Functions.dll Specifies that the output file name is Functions.dll. Normally the output
name is the same name as the first C# source file on the command line
(in this example, Factorial).
Factorial.cs Specifies the files to compile and place in the DLL.
DigitCounter.cs

Compiling the Client

19
To compile the program, make FunctionTest the current directory and type the following at the
command prompt:

copy ..\Functions\Functions.dll .
csc /out:FunctionTest.exe /R:Functions.DLL FunctionClient.cs
where:

/out:FunctionTest.exe Specifies that the output file name is FunctionTest.exe.


/R:Functions.DLL Specifies that Functions.DLL must be included when resolving
references. This DLL must be located in the current directory or have
a fully qualified path.
FunctionClient.cs Specifies the client source code.
This creates the executable file FunctionTest.exe.

File 1 - Factorial.cs

The following code calculates the factorial of the integer passed to the method (unlike Libraries
Sample, compile this to a library).

// Factorial.cs
// compile with: /target:library
using System;

// Declares a namespace. You need to package your libraries according


// to their namespace so the .NET runtime can correctly load the classes.
namespace Functions
{
public class Factorial
{
// The "Calc" static method calculates the factorial value for the
// specified integer passed in:
public static int Calc(int i)
{
return((i <= 1) ? 1 : (i * Calc(i-1)));
}
}
}

File 2 - DigitCounter.cs

20
The following code is used to count the number of digit characters in the passed string:

// DigitCounter.cs
// compile with: /target:library /out:Functions.dll /reference:Factorial.dll
using System;

// Declare the same namespace as the one in Factorial.cs. This simply


// allows types to be added to the same namespace.
namespace Functions
{
public class DigitCount
{
// The NumberOfDigits static method calculates the number of
// digit characters in the passed string:
public static int NumberOfDigits(string theString)
{
int count = 0;
for ( int i = 0; i < theString.Length; i++ )
{
if ( Char.IsDigit(theString[i]) )
{
count++;
}
}
return count;
}
}
}

File 3 - FunctionClient.cs

Once you build a library, it can be used by other programs. The following client program uses the
classes defined in the library. The basic function of the program is to take each command-line
parameter and attempt to compute the factorial value for each argument.

// FunctionClient.cs
// compile with: /reference:Functions.dll,Factorial.dll /out:FunctionTest.exe
// arguments: 3 5 10
using System;
// The following using directive makes the types defined in the Functions

21
// namespace available in this compilation unit:
using Functions;
class FunctionClient
{
public static void Main(string[] args)
{
Console.WriteLine("Function Client");

if ( args.Length == 0 )
{
Console.WriteLine("Usage: FunctionTest ... ");
return;
}

for ( int i = 0; i < args.Length; i++ )


{
int num = Int32.Parse(args[i]);
Console.WriteLine(
"The Digit Count for String [{0}] is [{1}]",
args[i],
// Invoke the NumberOfDigits static method in the
// DigitCount class:
DigitCount.NumberOfDigits(args[i]));
Console.WriteLine(
"The Factorial for [{0}] is [{1}]",
num,
// Invoke the Calc static method in the Factorial class:
Factorial.Calc(num) );
}
}
}

Output

The command line FunctionTest 3 5 10 uses the program FunctionTest to calculate the factorial
of the three integers 3, 5, and 10. It also displays the number of digits for each argument.

This run gives the output:

Function Client
The Digit Count for String [3] is [1]

22
The Factorial for [3] is [6]
The Digit Count for String [5] is [1]
The Factorial for [5] is [120]
The Digit Count for String [10] is [2]
The Factorial for [10] is [3628800]
Note To run the client executable (FunctionTest.exe), the file Functions.DLL must be in the
current directory, a child directory, or in the Global Assembly Cache. For more information see
Global Assembly Cache.

Versioning Tutorial

This tutorial demonstrates versioning in C# through the use of the override and new keywords.
Versioning maintains compatibility between base and derived classes as they evolve.

The C# language is designed such that versioning between base and derived classes in different
libraries can evolve and maintain backwards compatibility. This means, for example, that the
introduction of a new member in a base class with the same name as a member in a derived
class is not an error. It also means that a class must explicitly state whether a method is intended
to override an inherited method, or whether a method is a new method that simply hides a
similarly named inherited method.

In C#, methods are by default, not virtual. To make a method virtual, the virtual modifier has to be
used in the method declaration of the base class. The derived class can then override the base
virtual method by using the override keyword or hide the virtual method in the base class by
using the new keyword. If neither the override keyword nor the new keyword is specified, the
compiler will issue a warning and the method in the derived class will hide the method in the base
class. The following example shows these concepts in action.

Example

// versioning.cs
// CS0114 expected
public class MyBase
{
public virtual string Meth1()
{
return "MyBase-Meth1";
}
public virtual string Meth2()
{
return "MyBase-Meth2";

23
}
public virtual string Meth3()
{
return "MyBase-Meth3";
}
}

class MyDerived : MyBase


{
// Overrides the virtual method Meth1 using the override keyword:
public override string Meth1()
{
return "MyDerived-Meth1";
}
// Explicitly hide the virtual method Meth2 using the new
// keyword:
public new string Meth2()
{
return "MyDerived-Meth2";
}
// Because no keyword is specified in the following declaration
// a warning will be issued to alert the programmer that
// the method hides the inherited member MyBase.Meth3():
public string Meth3()
{
return "MyDerived-Meth3";
}

public static void Main()


{
MyDerived mD = new MyDerived();
MyBase mB = (MyBase) mD;

System.Console.WriteLine(mB.Meth1());
System.Console.WriteLine(mB.Meth2());
System.Console.WriteLine(mB.Meth3());
}
}

Output

24
MyDerived-Meth1
MyBase-Meth2
MyBase-Meth3

Code Discussion

Hiding a base class member from a derived class isn't an error in C#. This feature enables you to
make changes in the base class without breaking other libraries that inherit this base class. For
example, at some point you could have the following classes:

class Base {}
class Derived: Base
{
public void F() {}
}
At some later point, the base class could evolve to add a void method F() as follows:

class Base
{
public void F() {}
}
class Derived: Base
{
public void F() {}
}
Thus, in C#, both the base and derived classes can evolve freely and maintain binary
compatibility.

Collection Classes Tutorial

This tutorial shows how to implement a collection class that can be used with the foreach
statement.

The foreach statement is a convenient way to iterate over the elements of an array. It can also
enumerate the elements of a collection, provided that the collection class has implemented the
System.Collections.IEnumerator and System.Collections.IEnumerable interfaces.

Example 1

The following code sample illustrates how to write a collection class that can be used with
foreach. The class is a string tokenizer, similar to the C run-time function strtok.

// tokens.cs

25
using System;
// The System.Collections namespace is made available:
using System.Collections;

// Declare the Tokens class:


public class Tokens : IEnumerable
{
private string[] elements;

Tokens(string source, char[] delimiters)


{
// Parse the string into tokens:
elements = source.Split(delimiters);
}

// IEnumerable Interface Implementation:


// Declaration of the GetEnumerator() method
// required by IEnumerable
public IEnumerator GetEnumerator()
{
return new TokenEnumerator(this);
}

// Inner class implements IEnumerator interface:


private class TokenEnumerator : IEnumerator
{
private int position = -1;
private Tokens t;

public TokenEnumerator(Tokens t)
{
this.t = t;
}

// Declare the MoveNext method required by IEnumerator:


public bool MoveNext()
{
if (position < t.elements.Length - 1)
{
position++;

26
return true;
}
else
{
return false;
}
}

// Declare the Reset method required by IEnumerator:


public void Reset()
{
position = -1;
}

// Declare the Current property required by IEnumerator:


public object Current
{
get
{
return t.elements[position];
}
}
}

// Test Tokens, TokenEnumerator

static void Main()


{
// Testing Tokens by breaking the string into tokens:
Tokens f = new Tokens("This is a well-done program.",
new char[] {' ','-'});
foreach (string item in f)
{
Console.WriteLine(item);
}
}
}

Output

27
This
is
a
well
done
program.

Code Discussion

In the preceding example, the following code is used to Tokens by breaking "This is a well-done
program." into tokens (using ' ' and '-' as separators) and enumerating those tokens with the
foreach statement:

Tokens f = new Tokens("This is a well-done program.",


new char[] {' ','-'});
foreach (string item in f)
{
Console.WriteLine(item);
}
Notice that, internally, Tokens uses an array, which implements IEnumerator and IEnumerable
itself. The code sample could have leveraged the array's enumeration methods as its own, but
that would have defeated the purpose of this example.

In C#, it is not strictly necessary for a collection class to inherit from IEnumerable and
IEnumerator in order to be compatible with foreach; as long as the class has the required
GetEnumerator, MoveNext, Reset, and Current members, it will work with foreach. Omitting the
interfaces has the advantage of allowing you to define the return type of Current to be more
specific than object, thereby providing type-safety.

For example, starting with the sample code above, change the following lines:

public class Tokens // no longer inherits from IEnumerable


public TokenEnumerator GetEnumerator() // doesn't return an IEnumerator
public class TokenEnumerator // no longer inherits from IEnumerator
public string Current // type-safe: returns string, not object
Now, because Current returns a string, the compiler can detect when an incompatible type is
used in a foreach statement:

foreach (int item in f) // Error: cannot convert string to int


The disadvantage of omitting IEnumerable and IEnumerator is that the collection class is no
longer interoperable with the foreach statements (or equivalents) of other common language
runtime-compatible languages.

28
You can have the best of both worlds — type-safety within C# and interoperability with other
common language runtime-compatible languages — by inheriting from IEnumerable and
IEnumerator and using explicit interface implementation, as demonstrated in the following
example.

Example 2

This sample is equivalent in function to Example 1, but it provides additional type-safety in C#


while maintaining interoperability with other languages.

// tokens2.cs
using System;
using System.Collections;

public class Tokens: IEnumerable


{
private string[] elements;

Tokens(string source, char[] delimiters)


{
elements = source.Split(delimiters);
}

// IEnumerable Interface Implementation:

public TokenEnumerator GetEnumerator() // non-IEnumerable version


{
return new TokenEnumerator(this);
}

IEnumerator IEnumerable.GetEnumerator() // IEnumerable version


{
return (IEnumerator) new TokenEnumerator(this);
}

// Inner class implements IEnumerator interface:

public class TokenEnumerator: IEnumerator


{
private int position = -1;
private Tokens t;

29
public TokenEnumerator(Tokens t)
{
this.t = t;
}

public bool MoveNext()


{
if (position < t.elements.Length - 1)
{
position++;
return true;
}
else
{
return false;
}
}

public void Reset()


{
position = -1;
}

public string Current // non-IEnumerator version: type-safe


{
get
{
return t.elements[position];
}
}

object IEnumerator.Current // IEnumerator version: returns object


{
get
{
return t.elements[position];
}
}
}

30
// Test Tokens, TokenEnumerator

static void Main()


{
Tokens f = new Tokens("This is a well-done program.",
new char [] {' ','-'});
foreach (string item in f) // try changing string to int
{
Console.WriteLine(item);
}
}
}

Structs Tutorial

This tutorial presents the syntax and usage of structs. It also covers the important differences
between classes and structs.

This tutorial includes two examples. The first example shows you how to declare and use structs,
and the second example demonstrates the difference between structs and classes when
instances are passed to methods. You are also introduced to the following topics:

• Structs vs. Classes

• Heap or Stack?

• Constructors and Inheritance

• Attributes on Structs

Example 1

This example declares a struct with three members: a property, a method, and a private field. It
creates an instance of the struct and puts it to use:

// struct1.cs
using System;
struct SimpleStruct
{
private int xval;
public int X
{
get

31
{
return xval;
}
set
{
if (value < 100)
xval = value;
}
}
public void DisplayX()
{
Console.WriteLine("The stored value is: {0}", xval);
}
}

class TestClass
{
public static void Main()
{
SimpleStruct ss = new SimpleStruct();
ss.X = 5;
ss.DisplayX();
}
}

Output

The stored value is: 5

Structs vs. Classes


Structs may seem similar to classes, but there are important differences that you should be aware
of. First of all, classes are reference types and structs are value types. By using structs, you can
create objects that behave like the built-in types and enjoy their benefits as well.

Heap or Stack?
When you call the New operator on a class, it will be allocated on the heap. However, when you
instantiate a struct, it gets created on the stack. This will yield performance gains. Also, you will
not be dealing with references to an instance of a struct as you would with classes. You will be
working directly with the struct instance. Because of this, when passing a struct to a method, it's
passed by value instead of as a reference.

32
Example 2

This example shows that when a struct is passed to a method, a copy of the struct is passed, but
when a class instance is passed, a reference is passed.

// struct2.cs
using System;

class TheClass
{
public int x;
}

struct TheStruct
{
public int x;
}

class TestClass
{
public static void structtaker(TheStruct s)
{
s.x = 5;
}
public static void classtaker(TheClass c)
{
c.x = 5;
}
public static void Main()
{
TheStruct a = new TheStruct();
TheClass b = new TheClass();
a.x = 1;
b.x = 1;
structtaker(a);
classtaker(b);
Console.WriteLine("a.x = {0}", a.x);
Console.WriteLine("b.x = {0}", b.x);
}
}

33
Output

a.x = 1
b.x = 5

Code Discussion

The output of the example shows that only the value of the class field was changed when the
class instance was passed to the classtaker method. The struct field, however, did not change by
passing its instance to the structtaker method. This is because a copy of the struct was passed to
the structtaker method, while a reference to the class was passed to the classtaker method.

Constructors and Inheritance


Structs can declare constructors, but they must take parameters. It is an error to declare a default
(parameterless) constructor for a struct. Struct members cannot have initializers. A default
constructor is always provided to initialize the struct members to their default values.

When you create a struct object using the New operator, it gets created and the appropriate
constructor is called. Unlike classes, structs can be instantiated without using the New operator. If
you do not use New, the fields will remain unassigned and the object cannot be used until all the
fields are initialized.

There is no inheritance for structs as there is for classes. A struct cannot inherit from another
struct or class, and it cannot be the base of a class. Structs, however, inherit from the base class
object. A struct can implement interfaces, and it does that exactly as classes do. Here's a code
snippet of a struct implementing an interface:

interface IImage
{
void Paint();
}

struct Picture : IImage


{
public void Paint()
{
// painting code goes here
}
private int x, y, z; // other struct members
}

34
Attributes on Structs
By using attributes you can customize how structs are laid out in memory. For example, you can
create what's known as a union in C/C++ by using the StructLayout(LayoutKind.Explicit) and
FieldOffset attributes.

using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Explicit)]
struct TestUnion
{
[FieldOffset(0)]
public int i;
[FieldOffset(0)]
public double d;
[FieldOffset(0)]
public char c;
[FieldOffset(0)]
public byte b1;
}
In the preceding code segment, all of the fields of TestUnion start at the same location in memory.

The following is another example where fields start at different explicitly set locations:

using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Explicit)]
struct TestExplicit
{
[FieldOffset(0)]
public long lg;
[FieldOffset(0)]
public int i1;
[FieldOffset(4)]
public int i2;
[FieldOffset(8)]
public double d;
[FieldOffset(12)]
public char c;
[FieldOffset(14)]
public byte b1;
}

35
The two int fields, i1 and i2, share the same memory locations as lg. This sort of control over
struct layout is useful when using platform invocation.

Conclusion
Structs are simple to use and can prove to be useful at times. Just keep in mind that they're
created on the stack and that you're not dealing with references to them but dealing directly with
them. Whenever you have a need for a type that will be used often and is mostly just a piece of
data, structs might be a good option.

Indexers Tutorial

This tutorial shows how C# classes can declare indexers to provide array-like access to the
classes.

Defining an indexer allows you to create classes that act like "virtual arrays." Instances of that
class can be accessed using the [] array access operator. Defining an indexer in C# is similar to
defining operator [] in C++, but is considerably more flexible. For classes that encapsulate array-
or collection-like functionality, using an indexer allows the users of that class to use the array
syntax to access the class.

For example, suppose you want to define a class that makes a file appear as an array of bytes. If
the file were very large, it would be impractical to read the entire file into memory, especially if you
only wanted to read or change a few bytes. By defining a FileByteArray class, you could make the
file appear similar to an array of bytes, but actually do file input and output when a byte was read
or written.

In addition to the example below, an advanced topic on Creating an Indexed Property is


discussed in this tutorial.

Example

In this example, the class FileByteArray makes it possible to access a file as if it were a byte
array. The Reverse class reverses the bytes of the file. You can run this program to reverse the
bytes of any text file including the program source file itself. To change the reversed file back to
normal, run the program on the same file again.

// indexer.cs
// arguments: indexer.txt
using System;
using System.IO;

// Class to provide access to a large file


// as if it were a byte array.

36
public class FileByteArray
{
Stream stream; // Holds the underlying stream
// used to access the file.
// Create a new FileByteArray encapsulating a particular file.
public FileByteArray(string fileName)
{
stream = new FileStream(fileName, FileMode.Open);
}

// Close the stream. This should be the last thing done


// when you are finished.
public void Close()
{
stream.Close();
stream = null;
}

// Indexer to provide read/write access to the file.


public byte this[long index] // long is a 64-bit integer
{
// Read one byte at offset index and return it.
get
{
byte[] buffer = new byte[1];
stream.Seek(index, SeekOrigin.Begin);
stream.Read(buffer, 0, 1);
return buffer[0];
}
// Write one byte at offset index and return it.
set
{
byte[] buffer = new byte[1] {value};
stream.Seek(index, SeekOrigin.Begin);
stream.Write(buffer, 0, 1);
}
}

// Get the total length of the file.


public long Length

37
{
get
{
return stream.Seek(0, SeekOrigin.End);
}
}
}

// Demonstrate the FileByteArray class.


// Reverses the bytes in a file.
public class Reverse
{
public static void Main(String[] args)
{
// Check for arguments.
if (args.Length == 0)
{
Console.WriteLine("indexer <filename>");
return;
}

FileByteArray file = new FileByteArray(args[0]);


long len = file.Length;

// Swap bytes in the file to reverse it.


for (long i = 0; i < len / 2; ++i)
{
byte t;

// Note that indexing the "file" variable invokes the


// indexer on the FileByteStream class, which reads
// and writes the bytes in the file.
t = file[i];
file[i] = file[len - i - 1];
file[len - i - 1] = t;
}

file.Close();
}
}

38
Input: indexer.txt

To test the program you can use a text file with the following contents (this file is called Test.txt in
the Indexers Sample).

public class Hello1


{
public static void Main()
{
System.Console.WriteLine("Hello, World!");
}
}
To reverse the bytes of this file, compile the program and then use the command line:

indexer indexer.txt
To display the reversed file, enter the command:

Type indexer.txt

Sample Output

}
}
;)"!dlroW ,olleH"(eniLetirW.elosnoC.metsyS
{
)(niaM diov citats cilbup
{
1olleH ssalc cilbup

Code Discussion

• Since an indexer is accessed using the [] operator, it does not have a name. For indexer
declaration syntax, see Indexers.
• In the example above, the indexer is of type byte and takes a single index of type long
(64-bit integer). The Get accessor defines the code to read a byte from the file, while the
Set accessor defines the code to write a byte to the file. Inside the Set accessor, the
predefined parameter value has the value that is being assigned to the virtual array
element.
• An indexer must have at least one parameter. Although it is comparatively rare, an
indexer can have more than one parameter in order to simulate a multidimensional
"virtual array." Although integral parameters are the most common, the indexer parameter

39
can be of any type. For example, the standard Dictionary class provides an indexer with a
parameter of type Object.
• Although indexers are a powerful feature, it is important to use them only when the array-
like abstraction makes sense. Always carefully consider whether using regular method(s)
would be just as clear. For example, the following is a bad use of an indexer:

class Employee
{
// VERY BAD STYLE: using an indexer to access
// the salary of an employee.
public double this[int year]
{
get
{
// return employee's salary for a given year.
}
}
}
Although legal, an indexer with only a Get accessor is rarely good style. Strongly consider
using a method in this case.

• Indexers can be overloaded (for more information, see 10.8.1 Indexer overloading).

Indexed Properties Tutorial

This tutorial shows how to implement a class that uses indexed properties. Indexed properties
allow you to use a class that represents an array-like collection of several different kinds of things.
You should complete the Indexers Tutorial before working through this tutorial.

Suppose you want to write a class, Document, which encapsulates a lengthy section of text. To
allow easy implementation of various operations such as checking spelling, you might want to
view the document as a virtual array of words, as well as of characters.

The following example shows a technique for implementing such a class. For each "indexed
property," you define a nested class, which contains a reference back to the main class instance.
A readonly field on the main class provides access to an instance of the nested class that defines
each virtual array. Each of the nested classes defines an indexer, as well as other collection-like
methods (a Count property, for example). The following example shows this for "Words" and
"Characters."

40
Note Use this technique sparingly! Only use this pattern if the abstraction provided by using
array indexing operations significantly clarifies code that uses your class, and if the indexers have
both Get and Set accessors.

Example

In this example the Document class is defined. Two indexed properties, Words and Characters,
are used to perform some text operations on the Document object.

// indexedproperty.cs
using System;

public class Document


{
// Type allowing the document to be viewed like an array of words:
public class WordCollection
{
readonly Document document; // The containing document

internal WordCollection(Document d)
{
document = d;
}

// Helper function -- search character array "text", starting at


// character "begin", for word number "wordCount." Returns false
// if there are less than wordCount words. Sets "start" and
// length" to the position and length of the word within text:
private bool GetWord(char[] text, int begin, int wordCount,
out int start, out int length)
{
int end = text.Length;
int count = 0;
int inWord = -1;
start = length = 0;

for (int i = begin; i <= end; ++i)


{
bool isLetter = i < end && Char.IsLetterOrDigit(text[i]);

if (inWord >= 0)

41
{
if (!isLetter)
{
if (count++ == wordCount)
{
start = inWord;
length = i - inWord;
return true;
}
inWord = -1;
}
}
else
{
if (isLetter)
inWord = i;
}
}
return false;
}

// Indexer to get and set words of the containing document:


public string this[int index]
{
get
{
int start, length;
if (GetWord(document.TextArray, 0, index, out start,
out length))
return new string(document.TextArray, start, length);
else
throw new IndexOutOfRangeException();
}
set
{
int start, length;
if (GetWord(document.TextArray, 0, index, out start,
out length))
{
// Replace the word at start/length with the

42
// string "value":
if (length == value.Length)
{
Array.Copy(value.ToCharArray(), 0,
document.TextArray, start, length);
}
else
{
char[] newText =
new char[document.TextArray.Length +
value.Length - length];
Array.Copy(document.TextArray, 0, newText,
0, start);
Array.Copy(value.ToCharArray(), 0, newText,
start, value.Length);
Array.Copy(document.TextArray, start + length,
newText, start + value.Length,
document.TextArray.Length - start
- length);
document.TextArray = newText;
}
}
else
throw new IndexOutOfRangeException();
}
}

// Get the count of words in the containing document:


public int Count
{
get
{
int count = 0, start = 0, length = 0;
while (GetWord(document.TextArray, start + length, 0,
out start, out length))
++count;
return count;
}
}
}

43
// Type allowing the document to be viewed like an "array"
// of characters:
public class CharacterCollection
{
readonly Document document; // The containing document

internal CharacterCollection(Document d)
{
document = d;
}

// Indexer to get and set characters in the containing document:


public char this[int index]
{
get
{
return document.TextArray[index];
}
set
{
document.TextArray[index] = value;
}
}

// Get the count of characters in the containing document:


public int Count
{
get
{
return document.TextArray.Length;
}
}
}

// Because the types of the fields have indexers,


// these fields appear as "indexed properties":
public readonly WordCollection Words;
public readonly CharacterCollection Characters;

44
private char[] TextArray; // The text of the document.

public Document(string initialText)


{
TextArray = initialText.ToCharArray();
Words = new WordCollection(this);
Characters = new CharacterCollection(this);
}

public string Text


{
get
{
return new string(TextArray);
}
}
}

class Test
{
static void Main()
{
Document d = new Document(
"peter piper picked a peck of pickled peppers. How many pickled peppers did peter piper
pick?"
);

// Change word "peter" to "penelope":


for (int i = 0; i < d.Words.Count; ++i)
{
if (d.Words[i] == "peter")
d.Words[i] = "penelope";
}

// Change character "p" to "P"


for (int i = 0; i < d.Characters.Count; ++i)
{
if (d.Characters[i] == 'p')
d.Characters[i] = 'P';
}

45
Console.WriteLine(d.Text);
}
}

Output

PeneloPe PiPer Picked a Peck of Pickled PePPers. How many Pickled PePPers did PeneloPe
PiPer Pick?

User-Defined Conversions Tutorial

This tutorial shows how to define and use conversions to or from classes or structs.

C# allows programmers to declare conversions on classes or structs so that classes or structs


can be converted to and/or from other classes or structs, or basic types. Conversions are defined
like operators and are named for the type to which they convert.

In C#, conversions can be declared either as implicit, which occur automatically when required,
or explicit, which require a cast to be called. All conversions must be static, and must either take
the type the conversion is defined on, or return that type.

This tutorial introduces two examples. The first example shows how to declare and use
conversions, and the second example demonstrates conversions between structs.

Example 1

In this example, a RomanNumeral type is declared, and several conversions to and from it are
defined.

// conversion.cs
using System;

struct RomanNumeral
{
public RomanNumeral(int value)
{
this.value = value;
}
// Declare a conversion from an int to a RomanNumeral. Note the
// the use of the operator keyword. This is a conversion
// operator named RomanNumeral:
static public implicit operator RomanNumeral(int value)

46
{
// Note that because RomanNumeral is declared as a struct,
// calling new on the struct merely calls the constructor
// rather than allocating an object on the heap:
return new RomanNumeral(value);
}
// Declare an explicit conversion from a RomanNumeral to an int:
static public explicit operator int(RomanNumeral roman)
{
return roman.value;
}
// Declare an implicit conversion from a RomanNumeral to
// a string:
static public implicit operator string(RomanNumeral roman)
{
return("Conversion not yet implemented");
}
private int value;
}

class Test
{
static public void Main()
{
RomanNumeral numeral;

numeral = 10;

// Call the explicit conversion from numeral to int. Because it is


// an explicit conversion, a cast must be used:
Console.WriteLine((int)numeral);

// Call the implicit conversion to string. Because there is no


// cast, the implicit conversion to string is the only
// conversion that is considered:
Console.WriteLine(numeral);

// Call the explicit conversion from numeral to int and


// then the explicit conversion from int to short:
short s = (short)numeral;

47
Console.WriteLine(s);
}
}

Output

10
Conversion not yet implemented
10

Example 2

This example defines two structs, RomanNumeral and BinaryNumeral, and demonstrates
conversions between them.

// structconversion.cs
using System;

struct RomanNumeral
{
public RomanNumeral(int value)
{
this.value = value;
}
static public implicit operator RomanNumeral(int value)
{
return new RomanNumeral(value);
}
static public implicit operator RomanNumeral(BinaryNumeral binary)
{
return new RomanNumeral((int)binary);
}
static public explicit operator int(RomanNumeral roman)
{
return roman.value;
}
static public implicit operator string(RomanNumeral roman)
{
return("Conversion not yet implemented");
}

48
private int value;
}

struct BinaryNumeral
{
public BinaryNumeral(int value)
{
this.value = value;
}
static public implicit operator BinaryNumeral(int value)
{
return new BinaryNumeral(value);
}
static public implicit operator string(BinaryNumeral binary)
{
return("Conversion not yet implemented");
}
static public explicit operator int(BinaryNumeral binary)
{
return(binary.value);
}

private int value;


}

class Test
{
static public void Main()
{
RomanNumeral roman;
roman = 10;
BinaryNumeral binary;
// Perform a conversion from a RomanNumeral to a
// BinaryNumeral:
binary = (BinaryNumeral)(int)roman;
// Performs a conversion from a BinaryNumeral to a RomanNumeral.
// No cast is required:
roman = binary;
Console.WriteLine((int)binary);
Console.WriteLine(binary);

49
}
}

Output

10
Conversion not yet implemented

Code Discussion

• In the preceding example, the statement:

binary = (BinaryNumeral)(int)roman;
performs a conversion from a RomanNumeral to a BinaryNumeral. Because there is no
direct conversion from RomanNumeral to BinaryNumeral, a cast is used to convert from a
RomanNumeral to an int, and another cast to convert from an int to a BinaryNumeral.

• Also the statement:

roman = binary;
performs a conversion from a BinaryNumeral to a RomanNumeral. Because
RomanNumeral defines an implicit conversion from BinaryNumeral, no cast is required.

Operator Overloading Tutorial

This tutorial demonstrates how user-defined classes can overload operators.

Operator overloading permits user-defined operator implementations to be specified for


operations where one or both of the operands are of a user-defined class or struct type. The
tutorial contains two examples. The first example shows how to use operator overloading to
create a complex number class that defines complex addition. The second example shows how to
use operator overloading to implement a three-valued logical type.

Example 1

This example shows how you can use operator overloading to create a complex number class
Complex that defines complex addition. The program displays the imaginary and the real parts of
the numbers and the addition result using an override of the ToString method.

// complex.cs
using System;

50
public struct Complex
{
public int real;
public int imaginary;

public Complex(int real, int imaginary)


{
this.real = real;
this.imaginary = imaginary;
}

// Declare which operator to overload (+), the types


// that can be added (two Complex objects), and the
// return type (Complex):
public static Complex operator +(Complex c1, Complex c2)
{
return new Complex(c1.real + c2.real, c1.imaginary + c2.imaginary);
}
// Override the ToString method to display an complex number in the suitable format:
public override string ToString()
{
return(String.Format("{0} + {1}i", real, imaginary));
}

public static void Main()


{
Complex num1 = new Complex(2,3);
Complex num2 = new Complex(3,4);

// Add two Complex objects (num1 and num2) through the


// overloaded plus operator:
Complex sum = num1 + num2;

// Print the numbers and the sum using the overriden ToString method:
Console.WriteLine("First complex number: {0}",num1);
Console.WriteLine("Second complex number: {0}",num2);
Console.WriteLine("The sum of the two numbers: {0}",sum);

}
}

51
Output

First complex number: 2 + 3i


Second complex number: 3 + 4i
The sum of the two numbers: 5 + 7i

Example 2

This example shows how operator overloading can be used to implement a three-valued logical
type. The possible values of this type are DBBool.dbTrue, DBBool.dbFalse, and DBBool.dbNull,
where the dbNull member indicates an unknown value.

Note Defining the True and False operators is only useful for types that represent True, False,
and Null (neither True nor False), as used in databases.
// dbbool.cs
using System;

public struct DBBool


{
// The three possible DBBool values:
public static readonly DBBool dbNull = new DBBool(0);
public static readonly DBBool dbFalse = new DBBool(-1);
public static readonly DBBool dbTrue = new DBBool(1);
// Private field that stores -1, 0, 1 for dbFalse, dbNull, dbTrue:
int value;

// Private constructor. The value parameter must be -1, 0, or 1:


DBBool(int value)
{
this.value = value;
}

// Implicit conversion from bool to DBBool. Maps true to


// DBBool.dbTrue and false to DBBool.dbFalse:
public static implicit operator DBBool(bool x)
{
return x? dbTrue: dbFalse;
}

// Explicit conversion from DBBool to bool. Throws an


// exception if the given DBBool is dbNull, otherwise returns

52
// true or false:
public static explicit operator bool(DBBool x)
{
if (x.value == 0) throw new InvalidOperationException();
return x.value > 0;
}

// Equality operator. Returns dbNull if either operand is dbNull,


// otherwise returns dbTrue or dbFalse:
public static DBBool operator ==(DBBool x, DBBool y)
{
if (x.value == 0 || y.value == 0) return dbNull;
return x.value == y.value? dbTrue: dbFalse;
}

// Inequality operator. Returns dbNull if either operand is


// dbNull, otherwise returns dbTrue or dbFalse:
public static DBBool operator !=(DBBool x, DBBool y)
{
if (x.value == 0 || y.value == 0) return dbNull;
return x.value != y.value? dbTrue: dbFalse;
}

// Logical negation operator. Returns dbTrue if the operand is


// dbFalse, dbNull if the operand is dbNull, or dbFalse if the
// operand is dbTrue:
public static DBBool operator !(DBBool x)
{
return new DBBool(-x.value);
}

// Logical AND operator. Returns dbFalse if either operand is


// dbFalse, dbNull if either operand is dbNull, otherwise dbTrue:
public static DBBool operator &(DBBool x, DBBool y)
{
return new DBBool(x.value < y.value? x.value: y.value);
}

// Logical OR operator. Returns dbTrue if either operand is


// dbTrue, dbNull if either operand is dbNull, otherwise dbFalse:

53
public static DBBool operator |(DBBool x, DBBool y)
{
return new DBBool(x.value > y.value? x.value: y.value);
}

// Definitely true operator. Returns true if the operand is


// dbTrue, false otherwise:
public static bool operator true(DBBool x)
{
return x.value > 0;
}

// Definitely false operator. Returns true if the operand is


// dbFalse, false otherwise:
public static bool operator false(DBBool x)
{
return x.value < 0;
}

// Overload the conversion from DBBool to string:


public static implicit operator string(DBBool x)
{
return x.value > 0 ? "dbTrue"
: x.value < 0 ? "dbFalse"
: "dbNull";
}

// Override the Object.Equals(object o) method:


public override bool Equals(object o)
{
try
{
return (bool) (this == (DBBool) o);
}
catch
{
return false;
}
}

54
// Override the Object.GetHashCode() method:
public override int GetHashCode()
{
return value;
}

// Override the ToString method to convert DBBool to a string:


public override string ToString()
{
switch (value)
{
case -1:
return "DBBool.False";
case 0:
return "DBBool.Null";
case 1:
return "DBBool.True";
default:
throw new InvalidOperationException();
}
}
}

class Test
{
static void Main()
{
DBBool a, b;
a = DBBool.dbTrue;
b = DBBool.dbNull;

Console.WriteLine( "!{0} = {1}", a, !a);


Console.WriteLine( "!{0} = {1}", b, !b);
Console.WriteLine( "{0} & {1} = {2}", a, b, a & b);
Console.WriteLine( "{0} | {1} = {2}", a, b, a | b);
// Invoke the true operator to determine the Boolean
// value of the DBBool variable:
if (b)
Console.WriteLine("b is definitely true");
else

55
Console.WriteLine("b is not definitely true");
}
}

Output

!DBBool.True = DBBool.False
!DBBool.Null = DBBool.Null
DBBool.True & DBBool.Null = DBBool.Null
DBBool.True | DBBool.Null = DBBool.True
b is not definitely true

Delegates Tutorial

This tutorial demonstrates the delegate types. It shows how to map delegates to static and
instance methods, and how to combine them (multicast).

A delegate in C# is similar to a function pointer in C or C++. Using a delegate allows the


programmer to encapsulate a reference to a method inside a delegate object. The delegate object
can then be passed to code which can call the referenced method, without having to know at
compile time which method will be invoked. Unlike function pointers in C or C++, delegates are
object-oriented, type-safe, and secure.

A delegate declaration defines a type that encapsulates a method with a particular set of
arguments and return type. For static methods, a delegate object encapsulates the method to be
called. For instance methods, a delegate object encapsulates both an instance and a method on
the instance. If you have a delegate object and an appropriate set of arguments, you can invoke
the delegate with the arguments.

An interesting and useful property of a delegate is that it does not know or care about the class of
the object that it references. Any object will do; all that matters is that the method's argument
types and return type match the delegate's. This makes delegates perfectly suited for
"anonymous" invocation.

Note Delegates run under the caller's security permissions, not the declarer's permissions.
This tutorial includes two examples:

• Example 1 shows how to declare, instantiate, and call a delegate.

• Example 2 shows how to combine two delegates.

In addition, it discusses the following topics:

56
• Delegates and Events

• Delegates vs. Interfaces

Example 1

The following example illustrates declaring, instantiating, and using a delegate. The BookDB class
encapsulates a bookstore database that maintains a database of books. It exposes a method
ProcessPaperbackBooks, which finds all paperback books in the database and calls a delegate
for each one. The delegate type used is called ProcessBookDelegate. The Test class uses this
class to print out the titles and average price of the paperback books.

The use of delegates promotes good separation of functionality between the bookstore database
and the client code. The client code has no knowledge of how the books are stored or how the
bookstore code finds paperback books. The bookstore code has no knowledge of what
processing is done on the paperback books after it finds them.

// bookstore.cs
using System;

// A set of classes for handling a bookstore:


namespace Bookstore
{
using System.Collections;

// Describes a book in the book list:


public struct Book
{
public string Title; // Title of the book.
public string Author; // Author of the book.
public decimal Price; // Price of the book.
public bool Paperback; // Is it paperback?

public Book(string title, string author, decimal price, bool paperBack)


{
Title = title;
Author = author;
Price = price;
Paperback = paperBack;
}
}

57
// Declare a delegate type for processing a book:
public delegate void ProcessBookDelegate(Book book);

// Maintains a book database.


public class BookDB
{
// List of all books in the database:
ArrayList list = new ArrayList();

// Add a book to the database:


public void AddBook(string title, string author, decimal price, bool paperBack)
{
list.Add(new Book(title, author, price, paperBack));
}

// Call a passed-in delegate on each paperback book to process it:


public void ProcessPaperbackBooks(ProcessBookDelegate processBook)
{
foreach (Book b in list)
{
if (b.Paperback)
// Calling the delegate:
processBook(b);
}
}
}
}

// Using the Bookstore classes:


namespace BookTestClient
{
using Bookstore;

// Class to total and average prices of books:


class PriceTotaller
{
int countBooks = 0;
decimal priceBooks = 0.0m;

internal void AddBookToTotal(Book book)

58
{
countBooks += 1;
priceBooks += book.Price;
}

internal decimal AveragePrice()


{
return priceBooks / countBooks;
}
}

// Class to test the book database:


class Test
{
// Print the title of the book.
static void PrintTitle(Book b)
{
Console.WriteLine(" {0}", b.Title);
}

// Execution starts here.


static void Main()
{
BookDB bookDB = new BookDB();

// Initialize the database with some books:


AddBooks(bookDB);

// Print all the titles of paperbacks:


Console.WriteLine("Paperback Book Titles:");
// Create a new delegate object associated with the static
// method Test.PrintTitle:
bookDB.ProcessPaperbackBooks(new ProcessBookDelegate(PrintTitle));

// Get the average price of a paperback by using


// a PriceTotaller object:
PriceTotaller totaller = new PriceTotaller();
// Create a new delegate object associated with the nonstatic
// method AddBookToTotal on the object totaller:
bookDB.ProcessPaperbackBooks(new ProcessBookDelegate(totaller.AddBookToTotal));

59
Console.WriteLine("Average Paperback Book Price: ${0:#.##}",
totaller.AveragePrice());
}

// Initialize the book database with some test books:


static void AddBooks(BookDB bookDB)
{
bookDB.AddBook("The C Programming Language",
"Brian W. Kernighan and Dennis M. Ritchie", 19.95m, true);
bookDB.AddBook("The Unicode Standard 2.0",
"The Unicode Consortium", 39.95m, true);
bookDB.AddBook("The MS-DOS Encyclopedia",
"Ray Duncan", 129.95m, false);
bookDB.AddBook("Dogbert's Clues for the Clueless",
"Scott Adams", 12.00m, true);
}
}
}

Output

Paperback Book Titles:


The C Programming Language
The Unicode Standard 2.0
Dogbert's Clues for the Clueless
Average Paperback Book Price: $23.97

Code Discussion

• Declaring a delegate The following statement:

public delegate void ProcessBookDelegate(Book book);


declares a new delegate type. Each delegate type describes the number and types of the
arguments, and the type of the return value of methods that it can encapsulate.
Whenever a new set of argument types or return value type is needed, a new delegate
type must be declared.

• Instantiating a delegate Once a delegate type has been declared, a delegate object
must be created and associated with a particular method. Like all other objects, a new
delegate object is created with a new expression. When creating a delegate, however,

60
the argument passed to the new expression is special — it is written like a method call,
but without the arguments to the method.

The following statement:

bookDB.ProcessPaperbackBooks(new ProcessBookDelegate(PrintTitle));
creates a new delegate object associated with the static method Test.PrintTitle. The
following statement:

bookDB.ProcessPaperbackBooks(new
ProcessBookDelegate(totaller.AddBookToTotal));
creates a new delegate object associated with the nonstatic method AddBookToTotal on
the object totaller. In both cases, this new delegate object is immediately passed to the
ProcessPaperbackBooks method.

Note that once a delegate is created, the method it is associated with never changes —
delegate objects are immutable.

• Calling a delegate Once a delegate object is created, the delegate object is typically
passed to other code that will call the delegate. A delegate object is called by using the
name of the delegate object, followed by the parenthesized arguments to be passed to
the delegate. An example of a delegate call is:

processBook(b);
A delegate can either be called synchronously, as in this example, or asynchronously by
using BeginInvoke and EndInvoke methods.

Example 2

This example demonstrates composing delegates. A useful property of delegate objects is that
they can be composed using the "+" operator. A composed delegate calls the two delegates it
was composed from. Only delegates of the same type can be composed.

The "-" operator can be used to remove a component delegate from a composed delegate.

// compose.cs
using System;

delegate void MyDelegate(string s);

class MyClass
{
public static void Hello(string s)

61
{
Console.WriteLine(" Hello, {0}!", s);
}

public static void Goodbye(string s)


{
Console.WriteLine(" Goodbye, {0}!", s);
}

public static void Main()


{
MyDelegate a, b, c, d;

// Create the delegate object a that references


// the method Hello:
a = new MyDelegate(Hello);
// Create the delegate object b that references
// the method Goodbye:
b = new MyDelegate(Goodbye);
// The two delegates, a and b, are composed to form c:
c = a + b;
// Remove a from the composed delegate, leaving d,
// which calls only the method Goodbye:
d = c - a;

Console.WriteLine("Invoking delegate a:");


a("A");
Console.WriteLine("Invoking delegate b:");
b("B");
Console.WriteLine("Invoking delegate c:");
c("C");
Console.WriteLine("Invoking delegate d:");
d("D");
}
}

Output

Invoking delegate a:
Hello, A!

62
Invoking delegate b:
Goodbye, B!
Invoking delegate c:
Hello, C!
Goodbye, C!
Invoking delegate d:
Goodbye, D!

Delegates and Events


Delegates are ideally suited for use as events — notifications from one component to "listeners"
about changes in that component. For more information on the use of delegates for events, see
the Events Tutorial.

Delegates vs. Interfaces


Delegates and interfaces are similar in that they enable the separation of specification and
implementation. Multiple independent authors can produce implementations that are compatible
with an interface specification. Similarly, a delegate specifies the signature of a method, and
authors can write methods that are compatible with the delegate specification. When should you
use interfaces, and when should you use delegates?

Delegates are useful when:

• A single method is being called.

• A class may want to have multiple implementations of the method specification.

• It is desirable to allow using a static method to implement the specification.

• An event-like design pattern is desired (for more information, see the Events Tutorial).

• The caller has no need to know or obtain the object that the method is defined on.

• The provider of the implementation wants to "hand out" the implementation of the
specification to only a few select components.
• Easy composition is desired.

Interfaces are useful when:

• The specification defines a set of related methods that will be called.

• A class typically implements the specification only once.

• The caller of the interface wants to cast to or from the interface type to obtain other
interfaces or classes.

63
Events Tutorial

This tutorial shows how to declare, invoke, and hook up to events in C#.

An event in C# is a way for a class to provide notifications to clients of that class when some
interesting thing happens to an object. The most familiar use for events is in graphical user
interfaces; typically, the classes that represent controls in the interface have events that are
notified when the user does something to the control (for example, click a button).

Events, however, need not be used only for graphical interfaces. Events provide a generally
useful way for objects to signal state changes that may be useful to clients of that object. Events
are an important building block for creating classes that can be reused in a large number of
different programs.

Events are declared using delegates. If you have not yet studied the Delegates Tutorial, you
should do so before continuing. Recall that a delegate object encapsulates a method so that it
can be called anonymously. An event is a way for a class to allow clients to give it delegates to
methods that should be called when the event occurs. When the event occurs, the delegate(s)
given to it by its clients are invoked.

In addition to the examples on declaring, invoking, and hooking up to events, this tutorial also
introduces the following topics:

• Events and Inheritance

• Events in Interfaces

• .NET Framework Guidelines

Example 1

The following simple example shows a class, ListWithChangedEvent, which is similar to the
standard ArrayList class, but also invokes a Changed event whenever the contents of the list
change. Such a general-purpose class could be used in numerous ways in a large program.

For example, a word processor might maintain a list of the open documents. Whenever this list
changes, many different objects in the word processor might need to be notified so that the user
interface could be updated. By using events, the code that maintains the list of documents doesn't
need to know who needs to be notified — once the list of documents is changed, the event is
automatically invoked and every object that needs to be notified is correctly notified. By using
events, the modularity of the program is increased.

// events1.cs
using System;
namespace MyCollections

64
{
using System.Collections;

// A delegate type for hooking up change notifications.


public delegate void ChangedEventHandler(object sender, EventArgs e);

// A class that works just like ArrayList, but sends event


// notifications whenever the list changes.
public class ListWithChangedEvent: ArrayList
{
// An event that clients can use to be notified whenever the
// elements of the list change.
public event ChangedEventHandler Changed;

// Invoke the Changed event; called whenever list changes


protected virtual void OnChanged(EventArgs e)
{
if (Changed != null)
Changed(this, e);
}

// Override some of the methods that can change the list;


// invoke event after each
public override int Add(object value)
{
int i = base.Add(value);
OnChanged(EventArgs.Empty);
return i;
}

public override void Clear()


{
base.Clear();
OnChanged(EventArgs.Empty);
}

public override object this[int index]


{
set
{

65
base[index] = value;
OnChanged(EventArgs.Empty);
}
}
}
}

namespace TestEvents
{
using MyCollections;

class EventListener
{
private ListWithChangedEvent List;

public EventListener(ListWithChangedEvent list)


{
List = list;
// Add "ListChanged" to the Changed event on "List".
List.Changed += new ChangedEventHandler(ListChanged);
}

// This will be called whenever the list changes.


private void ListChanged(object sender, EventArgs e)
{
Console.WriteLine("This is called when the event fires.");
}

public void Detach()


{
// Detach the event and delete the list
List.Changed -= new ChangedEventHandler(ListChanged);
List = null;
}
}

class Test
{
// Test the ListWithChangedEvent class.
public static void Main()

66
{
// Create a new list.
ListWithChangedEvent list = new ListWithChangedEvent();

// Create a class that listens to the list's change event.


EventListener listener = new EventListener(list);

// Add and remove items from the list.


list.Add("item 1");
list.Clear();
listener.Detach();
}
}
}

Output

This is called when the event fires.


This is called when the event fires.

Code Discussion

• Declaring an event To declare an event inside a class, first a delegate type for the
event must be declared, if none is already declared.

public delegate void ChangedEventHandler(object sender, EventArgs e);


The delegate type defines the set of arguments that are passed to the method that
handles the event. Multiple events can share the same delegate type, so this step is only
necessary if no suitable delegate type has already been declared.

Next, the event itself is declared.

public event ChangedEventHandler Changed;


An event is declared like a field of delegate type, except that the keyword event precedes
the event declaration, following the modifiers. Events usually are declared public, but any
accessibility modifier is allowed.

• Invoking an event Once a class has declared an event, it can treat that event just like a
field of the indicated delegate type. The field will either be null, if no client has hooked up
a delegate to the event, or else it refers to a delegate that should be called when the
event is invoked. Thus, invoking an event is generally done by first checking for null and
then calling the event.

67
if (Changed != null)
Changed(this, e);
Invoking an event can only be done from within the class that declared the event.

• Hooking up to an event From outside the class that declared it, an event looks like a
field, but access to that field is very restricted. The only things that can be done are:
• Compose a new delegate onto that field.

• Remove a delegate from a (possibly composite) field.

This is done with the += and -= operators. To begin receiving event invocations, client
code first creates a delegate of the event type that refers to the method that should be
invoked from the event. Then it composes that delegate onto any other delegates that the
event might be connected to using +=.

// Add "ListChanged" to the Changed event on "List":


List.Changed += new ChangedEventHandler(ListChanged);
When the client code is done receiving event invocations, it removes its delegate from the
event by using operator -=.

// Detach the event and delete the list:


List.Changed -= new ChangedEventHandler(ListChanged);

Events and Inheritance


When creating a general component that can be derived from, what seems to be a problem
sometimes arises with events. Since events can only be invoked from within the class that
declared them, derived classes cannot directly invoke events declared within the base class.
Although this is sometimes what is desired, often it is appropriate to give the derived class the
freedom to invoke the event. This is typically done by creating a protected invoking method for the
event. By calling this invoking method, derived classes can invoke the event. For even more
flexibility, the invoking method is often declared as virtual, which allows the derived class to
override it. This allows the derived class to intercept the events that the base class is invoking,
possibly doing its own processing of them.

In the preceding example, this has been done with the OnChanged method. A derived class could
call or override this method if it needed to.

Events in Interfaces
One other difference between events and fields is that an event can be placed in an interface
while a field cannot. When implementing the interface, the implementing class must supply a
corresponding event in the class that implements the interface.

68
.NET Framework Guidelines
Although the C# language allows events to use any delegate type, the .NET Framework has
some stricter guidelines on the delegate types that should be used for events. If you intend for
your component to be used with the .NET Framework, you probably will want to follow these
guidelines.

The .NET Framework guidelines indicate that the delegate type used for an event should take two
parameters, an "object source" parameter indicating the source of the event, and an "e"
parameter that encapsulates any additional information about the event. The type of the "e"
parameter should derive from the EventArgs class. For events that do not use any additional
information, the .NET Framework has already defined an appropriate delegate type:
EventHandler.

Example 2

The following example is a modified version of Example 1 that follows the .NET Framework
guidelines. The example uses the EventHandler delegate type.

// events2.cs
using System;
namespace MyCollections
{
using System.Collections;

// A class that works just like ArrayList, but sends event


// notifications whenever the list changes:
public class ListWithChangedEvent: ArrayList
{
// An event that clients can use to be notified whenever the
// elements of the list change:
public event EventHandler Changed;

// Invoke the Changed event; called whenever list changes:


protected virtual void OnChanged(EventArgs e)
{
if (Changed != null)
Changed(this,e);
}

// Override some of the methods that can change the list;


// invoke event after each:

69
public override int Add(object value)
{
int i = base.Add(value);
OnChanged(EventArgs.Empty);
return i;
}

public override void Clear()


{
base.Clear();
OnChanged(EventArgs.Empty);
}

public override object this[int index]


{
set
{
base[index] = value;
OnChanged(EventArgs.Empty);
}
}
}
}

namespace TestEvents
{
using MyCollections;

class EventListener
{
private ListWithChangedEvent List;

public EventListener(ListWithChangedEvent list)


{
List = list;
// Add "ListChanged" to the Changed event on "List":
List.Changed += new EventHandler(ListChanged);
}

// This will be called whenever the list changes:

70
private void ListChanged(object sender, EventArgs e)
{
Console.WriteLine("This is called when the event fires.");
}

public void Detach()


{
// Detach the event and delete the list:
List.Changed -= new EventHandler(ListChanged);
List = null;
}
}

class Test
{
// Test the ListWithChangedEvent class:
public static void Main()
{
// Create a new list:
ListWithChangedEvent list = new ListWithChangedEvent();

// Create a class that listens to the list's change event:


EventListener listener = new EventListener(list);

// Add and remove items from the list:


list.Add("item 1");
list.Clear();
listener.Detach();
}
}
}

Output

This is called when the event fires.


Explicit Interface Implementation Tutorial
This tutorial demonstrates how to explicitly implement interface members and how to access
those members from the interface instances.

A class that implements an interface can explicitly implement a member of that interface. When a
member is explicitly implemented, it cannot be accessed through a class instance, but only

71
through an instance of the interface. This tutorial contains two examples. The first example
illustrates how to explicitly implement and access interface members. The second example shows
how to implement two interfaces that have the same member names.

Example 1

This example declares an interface IDimensions, and a class Box, which explicitly implements the
interface members Length and Width. The members are accessed through the interface instance
myDimensions.

// explicit1.cs
interface IDimensions
{
float Length();
float Width();
}

class Box : IDimensions


{
float lengthInches;
float widthInches;

public Box(float length, float width)


{
lengthInches = length;
widthInches = width;
}
// Explicit interface member implementation:
float IDimensions.Length()
{
return lengthInches;
}
// Explicit interface member implementation:
float IDimensions.Width()
{
return widthInches;
}

public static void Main()


{
// Declare a class instance "myBox":

72
Box myBox = new Box(30.0f, 20.0f);
// Declare an interface instance "myDimensions":
IDimensions myDimensions = (IDimensions) myBox;
// Print out the dimensions of the box:
/* The following commented lines would produce compilation
errors because they try to access an explicitly implemented
interface member from a class instance: */
//System.Console.WriteLine("Length: {0}", myBox.Length());
//System.Console.WriteLine("Width: {0}", myBox.Width());
/* Print out the dimensions of the box by calling the methods
from an instance of the interface: */
System.Console.WriteLine("Length: {0}", myDimensions.Length());
System.Console.WriteLine("Width: {0}", myDimensions.Width());
}
}

Output

Length: 30
Width: 20

Code Discussion

• Notice that the following lines, in the Main method, are commented out because they
would produce compilation errors. An interface member that is explicitly implemented
cannot be accessed from a class instance:

//System.Console.WriteLine("Length: {0}", myBox.Length());


//System.Console.WriteLine("Width: {0}", myBox.Width());

• Notice also that the following lines, in the Main method, successfully print out the
dimensions of the box because the methods are being called from an instance of the
interface:

System.Console.WriteLine("Length: {0}", myDimensions.Length());


System.Console.WriteLine("Width: {0}", myDimensions.Width());

Example 2

Explicit interface implementation also allows the programmer to inherit two interfaces that share
the same member names and give each interface member a separate implementation. This

73
example displays the dimensions of a box in both metric and English units. The Box class inherits
two interfaces IEnglishDimensions and IMetricDimensions, which represent the different
measurement systems. Both interfaces have identical member names, Length and Width.

// explicit2.cs
// Declare the English units interface:
interface IEnglishDimensions
{
float Length();
float Width();
}
// Declare the metric units interface:
interface IMetricDimensions
{
float Length();
float Width();
}
// Declare the "Box" class that implements the two interfaces:
// IEnglishDimensions and IMetricDimensions:
class Box : IEnglishDimensions, IMetricDimensions
{
float lengthInches;
float widthInches;
public Box(float length, float width)
{
lengthInches = length;
widthInches = width;
}
// Explicitly implement the members of IEnglishDimensions:
float IEnglishDimensions.Length()
{
return lengthInches;
}
float IEnglishDimensions.Width()
{
return widthInches;
}
// Explicitly implement the members of IMetricDimensions:
float IMetricDimensions.Length()
{
return lengthInches * 2.54f;

74
}
float IMetricDimensions.Width()
{
return widthInches * 2.54f;
}
public static void Main()
{
// Declare a class instance "myBox":
Box myBox = new Box(30.0f, 20.0f);
// Declare an instance of the English units interface:
IEnglishDimensions eDimensions = (IEnglishDimensions) myBox;
// Declare an instance of the metric units interface:
IMetricDimensions mDimensions = (IMetricDimensions) myBox;
// Print dimensions in English units:
System.Console.WriteLine("Length(in): {0}", eDimensions.Length());
System.Console.WriteLine("Width (in): {0}", eDimensions.Width());
// Print dimensions in metric units:
System.Console.WriteLine("Length(cm): {0}", mDimensions.Length());
System.Console.WriteLine("Width (cm): {0}", mDimensions.Width());
}
}

Output

Length(in): 30
Width (in): 20
Length(cm): 76.2
Width (cm): 50.8

Code Discussion

If you want to make the default measurements in English units, implement the methods Length
and Width normally, and explicitly implement the Length and Width methods from the
IMetricDimensions interface:

// Normal implementation:
public float Length()
{
return lengthInches;
}
public float Width()

75
{
return widthInches;
}
// Explicit implementation:
float IMetricDimensions.Length()
{
return lengthInches * 2.54f;
}
float IMetricDimensions.Width()
{
return widthInches * 2.54f;
}
In this case, you can access the English units from the class instance and access the metric units
from the interface instance:

System.Console.WriteLine("Length(in): {0}", myBox.Length());


System.Console.WriteLine("Width (in): {0}", myBox.Width());
System.Console.WriteLine("Length(cm): {0}", mDimensions.Length());
System.Console.WriteLine("Width (cm): {0}", mDimensions.Width());

Conditional Methods Tutorial

This tutorial demonstrates conditional methods, which provide a powerful mechanism by which
calls to methods can be included or omitted depending on whether a preprocessor symbol is
defined.

Conditional methods allow developers to create methods whose calls can be placed in the code
and then either included or omitted during compilation based on a preprocessing symbol.

Suppose that you want to enable some assertion code in the debug builds and disable it in the
retail builds. In C++, there is more than one way to have this functionality in your code, for
example:

• Using #ifdef, define both debug and release versions of a macro. The debug version calls
the tracing code, and the release version does nothing. Because C# doesn't support
macros, this method doesn't work.
• Have two implementations of the code being called. That is, in the debug version, have
full functionality, and in the retail version, have empty stubs for the methods. Users then
choose which one to include when linking the project. The problem with this approach is
that the retail builds contain calls to empty methods, and the configuration is more
complex.

76
C# conditional methods provide a simple solution to this problem that is similar to the first
approach listed above. There are two basic mechanisms to do this:

• #define the preprocessing identifier in the source code directly. (See an example of this
approach in Conditional.)
• Define the preprocessing identifier on the C# command line via the /define option (/d).
This approach is used in the following example.

Conditional methods are used in the .NET Framework. The System.Diagnostics namespace
contains a number of classes that support tracing and debugging in applications. Use the
System.Diagnostics.Trace and System.Diagnostics.Debug classes to add sophisticated
tracing and debugging to your application (functionality that can be compiled out of your retail
builds through the use of conditional methods).

The example below shows how to implement a very simple tracing mechanism using conditional
methods. System.Diagnostics.Trace provides much more sophisticated tracing mechanisms,
but it uses the fundamental mechanism below to provide this functionality.

Example

This example consists of two source files: the first file is the library that provides a tracing
mechanism, and the second file is the client program that uses this library.

File #1: Creating Conditional Methods

The code below shows a simple library that provides a tracing mechanism that displays trace
messages to the system console. Clients can embed trace calls in the code and then be able to
control whether the tracing gets called by defining symbols in their own compilation phase.

Copy Code
// CondMethod.cs
// compile with: /target:library /d:DEBUG
using System;
using System.Diagnostics;
namespace TraceFunctions
{
public class Trace
{
[Conditional("DEBUG")]
public static void Message(string traceMessage)
{

77
Console.WriteLine("[TRACE] - " + traceMessage);
}
}
}

Code Discussion

The following line:

[Conditional("DEBUG")]
marks the Message method as being conditional (via the Conditional attribute). The Conditional
attribute takes one parameter — the preprocessing identifier that controls whether the method call
is included when clients are compiled. If the preprocessing identifier is defined, the method is
called; otherwise, the call is never inserted in the client's code.

There are restrictions on which methods can be marked as conditional; see 17.4.2 The
Conditional attribute in the C# Language Specification for more information.

File #2: Using the Conditional Method

The following client program uses the Trace class defined in file #1 to do some simple tracing.

// TraceTest.cs
// compile with: /reference:CondMethod.dll
// arguments: A B C
using System;
using TraceFunctions;

public class TraceClient


{
public static void Main(string[] args)
{
Trace.Message("Main Starting");

if (args.Length == 0)
{
Console.WriteLine("No arguments have been passed");
}
else
{
for( int i=0; i < args.Length; i++)
{

78
Console.WriteLine("Arg[{0}] is [{1}]",i,args[i]);
}
}

Trace.Message("Main Ending");
}
}

Code Discussion

Conditional code is included in client code depending on whether the preprocessing identifier is
defined when the client code gets compiled.

Compiling with client code with the /d:DEBUG flag means that the compiler inserts the call to the
Trace method. If the symbol is not defined, the call is never made.

Sample Run

The command:

tracetest A B C
gives the following output:

[TRACE] - Main Starting


Arg[0] is [A]
Arg[1] is [B]
Arg[2] is [C]
[TRACE] - Main Ending
The command:

tracetest
gives the following output:

[TRACE] - Main Starting


No arguments have been passed
[TRACE] - Main Ending

XML Documentation Tutorial


This tutorial shows how to document code using XML.

79
C# provides a mechanism for developers to document their code using XML. In source code files,
lines that begin with /// and that precede a user-defined type such as a class, delegate, or
interface; a member such as a field, event, property, or method; or a namespace declaration can
be processed as comments and placed in a file.

Example

The following sample provides a basic overview of a type that has been documented. To compile
the example, type the following command line:

csc XMLsample.cs /doc:XMLsample.xml


This will create the XML file XMLsample.xml, which you can view in your browser or by using the
TYPE command.

// XMLsample.cs
// compile with: /doc:XMLsample.xml
using System;

/// <summary>
/// Class level summary documentation goes here.</summary>
/// <remarks>
/// Longer comments can be associated with a type or member
/// through the remarks tag</remarks>
public class SomeClass
{
/// <summary>
/// Store for the name property</summary>
private string myName = null;

/// <summary>
/// The class constructor. </summary>
public SomeClass()
{
// TODO: Add Constructor Logic here
}

/// <summary>
/// Name property </summary>
/// <value>
/// A value tag is used to describe the property value</value>
public string Name

80
{
get
{
if ( myName == null )
{
throw new Exception("Name is null");
}

return myName;
}
}

/// <summary>
/// Description for SomeMethod.</summary>
/// <param name="s"> Parameter description for s goes here</param>
/// <seealso cref="String">
/// You can use the cref attribute on any tag to reference a type or member
/// and the compiler will check that the reference exists. </seealso>
public void SomeMethod(string s)
{
}

/// <summary>
/// Some other method. </summary>
/// <returns>
/// Return results are described through the returns tag.</returns>
/// <seealso cref="SomeMethod(string)">
/// Notice the use of the cref attribute to reference a specific method </seealso>
public int SomeOtherMethod()
{
return 0;
}

/// <summary>
/// The entry point for the application.
/// </summary>
/// <param name="args"> A list of command line arguments</param>
public static int Main(String[] args)
{
// TODO: Add code to start application here

81
return 0;
}
}

Code Discussion

XML documentation starts with ///. When you create a new project, the wizards put some
starter /// lines in for you. The processing of these comments has some restrictions:

• The documentation must be well-formed XML. If the XML is not well-formed, a warning is
generated and the documentation file will contain a comment saying that an error was
encountered. For more information on well-formed XML, see XML Glossary.
• Developers are free to create their own set of tags. There is a recommended set of tags
(see the Further Reading section). Some of the recommended tags have special
meanings:
• The <param> tag is used to describe parameters. If used, the compiler will verify
that the parameter exists and that all parameters are described in the documentation.
If the verification failed, the compiler issues a warning.
• The cref attribute can be attached to any tag to provide a reference to a code
element. The compiler will verify that this code element exists. If the verification failed,
the compiler issues a warning. The compiler also respects any using statements when
looking for a type described in the cref attribute.
• The <summary> tag is used by IntelliSense inside Visual Studio to display
additional information about a type or member.

Sample Output

Here is the resulting XML file from the class above:

<?xml version="1.0"?>
<doc>
<assembly>
<name>xmlsample</name>
</assembly>
<members>
<member name="T:SomeClass">
<summary>
Class level summary documentation goes here.</summary>
<remarks>

82
Longer comments can be associated with a type or member
through the remarks tag</remarks>
</member>
<member name="F:SomeClass.myName">
<summary>
Store for the name property</summary>
</member>
<member name="M:SomeClass.#ctor">
<summary>The class constructor.</summary>
</member>
<member name="M:SomeClass.SomeMethod(System.String)">
<summary>
Description for SomeMethod.</summary>
<param name="s"> Parameter description for s goes here</param>
<seealso cref="T:System.String">
You can use the cref attribute on any tag to reference a type or member
and the compiler will check that the reference exists. </seealso>
</member>
<member name="M:SomeClass.SomeOtherMethod">
<summary>
Some other method. </summary>
<returns>
Return results are described through the returns tag.</returns>
<seealso cref="M:SomeClass.SomeMethod(System.String)">
Notice the use of the cref attribute to reference a specific method </seealso>
</member>
<member name="M:SomeClass.Main(System.String[])">
<summary>
The entry point for the application.
</summary>
<param name="args"> A list of command line arguments</param>
</member>
<member name="P:SomeClass.Name">
<summary>
Name property </summary>
<value>
A value tag is used to describe the property value</value>
</member>
</members>
</doc>

83
Note The XML file does not provide full information about the type and members (for example, it
does not contain any type information). To get full information about a type or member, the
documentation file must be used in conjunction with reflection on the actual type or member.

Platform Invoke Tutorial

Platform Invocation Services (PInvoke) allows managed code to call unmanaged functions that
are implemented in a DLL.

This tutorial shows you what you need to do to be able to call unmanaged DLL functions from C#.
The attributes discussed in the tutorial allow you to call these functions and have data types be
marshaled correctly.

There are two ways that C# code can directly call unmanaged code:

• Directly call a function exported from a DLL.

• Call an interface method on a COM object (for more information, see COM Interop Part 1:
C# Client Tutorial).

For both techniques, you must provide the C# compiler with a declaration of the unmanaged
function, and you may also need to provide the C# compiler with a description of how to marshal
the parameters and return value to and from the unmanaged code.

The tutorial consists of the following topics:

• Calling a DLL Export Directly from C#

• Default Marshaling and Specifying Custom Marshaling for Parameters to Unmanaged


Methods
• Specifying Custom Marshaling for User-Defined Structs

• Registering Callback Methods

The tutorial includes the following examples:

• Example 1 Using DllImport

• Example 2 Overriding Default Marshaling

• Example 3 Specifying Custom Marshaling

Calling a DLL Export Directly from C#


To declare a method as having an implementation from a DLL export, do the following:

• Declare the method with the static and extern C# keywords.

84
• Attach the DllImport attribute to the method. The DllImport attribute allows you to
specify the name of the DLL that contains the method. The common practice is to name
the C# method the same as the exported method, but you can also use a different name
for the C# method.
• Optionally, specify custom marshaling information for the method's parameters and return
value, which will override the .NET Framework default marshaling.

Example 1

This example shows you how to use the DllImport attribute to output a message by calling puts
from msvcrt.dll.

// PInvokeTest.cs
using System;
using System.Runtime.InteropServices;

class PlatformInvokeTest
{
[DllImport("msvcrt.dll")]
public static extern int puts(string c);
[DllImport("msvcrt.dll")]
internal static extern int _flushall();

public static void Main()


{
puts("Test");
_flushall();
}
}

Output

Test

Code Discussion

The preceding example shows the minimum requirements for declaring a C# method that is
implemented in an unmanaged DLL. The method PlatformInvokeTest.puts is declared with the
static and extern modifiers and has the DllImport attribute which tells the compiler that the
implementation comes from msvcrt.dll, using the default name of puts. To use a different name for

85
the C# method such as putstring, you must use the EntryPoint option in the DllImport attribute,
that is:

[DllImport("msvcrt.dll", EntryPoint="puts")]
For more information on the syntax of the DllImport attribute, see DllImportAttribute Class.

Default Marshaling and Specifying Custom Marshaling for Parameters to Unmanaged


Methods
When calling an unmanaged function from C# code, the common language runtime must marshal
the parameters and return values.

For every .NET Framework type there is a default unmanaged type, which the common language
runtime will use to marshal data across a managed to unmanaged function call. For example, the
default marshaling for C# string values is to the type LPTSTR (pointer to TCHAR char buffer).
You can override the default marshaling using the MarshalAs attribute in the C# declaration of
the unmanaged function.

Example 2

This example uses the DllImport attribute to output a string. It also shows you how to override the
default marshaling of the function parameters by using the MarshalAs attribute.

// Marshal.cs
using System;
using System.Runtime.InteropServices;

class PlatformInvokeTest
{
[DllImport("msvcrt.dll")]
public static extern int puts(
[MarshalAs(UnmanagedType.LPStr)]
string m);
[DllImport("msvcrt.dll")]
internal static extern int _flushall();

public static void Main()


{
puts("Hello World!");
_flushall();
}
}

86
Output

When you run this example, the string,

Hello World!
will display at the console.

Code Discussion

In the preceding example, the default marshaling for the parameter to the puts function has been
overridden from the default of LPTSTR to LPSTR.

The MarshalAs attribute can be placed on method parameters, method return values, and fields
of structs and classes. To set the marshaling of a method return value, place the MarshalAs
attribute in an attribute block on the method with the return attribute location override. For
example, to explicitly set the marshaling for the return value of the puts method:

...
[DllImport("msvcrt.dll")]
[return : MarshalAs(UnmanagedType.I4)]
public static extern int puts(
...
For more information on the syntax of the MarshalAs attribute, see MarshalAsAttribute Class.

Note The In and Out attributes can be used to annotate parameters to unmanaged methods.
They behave in a similar manner to the in and out modifiers in MIDL source files. Note that the
Out attribute is different from the C# parameter modifier, out. For more information on the In and
Out attributes, see InAttribute Class and OutAttribute Class.

Specifying Custom Marshaling for User-Defined Structs


You can specify custom marshaling attributes for fields of structs and classes passed to or from
unmanaged functions. You do this by adding MarshalAs attributes to the fields of the struct or
class. You must also use the StructLayout attribute to set the layout of the struct, optionally to
control the default marshaling of string members, and to set the default packing size.

Example 3

This example demonstrates how to specify custom marshaling attributes for a struct.

Consider the following C structure:

typedef struct tagLOGFONT


{
LONG lfHeight;

87
LONG lfWidth;
LONG lfEscapement;
LONG lfOrientation;
LONG lfWeight;
BYTE lfItalic;
BYTE lfUnderline;
BYTE lfStrikeOut;
BYTE lfCharSet;
BYTE lfOutPrecision;
BYTE lfClipPrecision;
BYTE lfQuality;
BYTE lfPitchAndFamily;
TCHAR lfFaceName[LF_FACESIZE];
} LOGFONT;
In C#, you can describe the preceding struct by using the StructLayout and MarshalAs
attributes as follows:

// logfont.cs
// compile with: /target:module
using System;
using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Sequential)]
public class LOGFONT
{
public const int LF_FACESIZE = 32;
public int lfHeight;
public int lfWidth;
public int lfEscapement;
public int lfOrientation;
public int lfWeight;
public byte lfItalic;
public byte lfUnderline;
public byte lfStrikeOut;
public byte lfCharSet;
public byte lfOutPrecision;
public byte lfClipPrecision;
public byte lfQuality;
public byte lfPitchAndFamily;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=LF_FACESIZE)]
public string lfFaceName;

88
}
For more information on the syntax of the StructLayout attribute, see StructLayoutAttribute
Class.

The structure can then be used in C# code as shown below:

// pinvoke.cs
// compile with: /addmodule:logfont.netmodule
using System;
using System.Runtime.InteropServices;

class PlatformInvokeTest
{
[DllImport("gdi32.dll", CharSet=CharSet.Auto)]
public static extern IntPtr CreateFontIndirect(
[In, MarshalAs(UnmanagedType.LPStruct)]
LOGFONT lplf // characteristics
);

[DllImport("gdi32.dll")]
public static extern bool DeleteObject(
IntPtr handle
);

public static void Main()


{
LOGFONT lf = new LOGFONT();
lf.lfHeight = 9;
lf.lfFaceName = "Arial";
IntPtr handle = CreateFontIndirect(lf);

if (IntPtr.Zero == handle)
{
Console.WriteLine("Can't creates a logical font.");
}
else
{

if (IntPtr.Size == 4)
Console.WriteLine("{0:X}", handle.ToInt32());
else

89
Console.WriteLine("{0:X}", handle.ToInt64());

// Delete the logical font created.


if (!DeleteObject(handle))
Console.WriteLine("Can't delete the logical font");
}
}
}

Sample Run

C30A0AE5

Code Discussion

In the preceding example, the CreateFontIndirect method is using a parameter of the type
LOGFONT. The MarshalAs and In attributes are used to qualify the parameter. The program
displays the numeric value returned by the method as a hexadecimal uppercase string.

Registering Callback Methods


To register a managed callback that calls an unmanaged function, declare a delegate with the
same argument list and pass an instance of it via PInvoke. On the unmanaged side it will appear
as a function pointer. For more information about PInvoke and callback, see A Closer Look at
Platform Invoke.

For example, consider the following unmanaged function, MyFunction, which requires callback as
one of the arguments:

typedef void (__stdcall *PFN_MYCALLBACK)();


int __stdcall MyFunction(PFN_ MYCALLBACK callback);
To call MyFunction from managed code, declare the delegate, attach DllImport to the function
declaration, and optionally marshal any parameters or the return value:

public delegate void MyCallback();


[DllImport("MYDLL.DLL")]
public static extern void MyFunction(MyCallback callback);
Also, make sure the lifetime of the delegate instance covers the lifetime of the unmanaged code;
otherwise, the delegate will not be available after it is garbage-collected.

90
COM Interop Part 1: C# Client Tutorial

COM Interop provides access to existing COM components without requiring that the original
component be modified. When you want to incorporate COM code into a managed application,
import the relevant COM types by using a COM Interop utility (TlbImp.exe) for that purpose. Once
imported, the COM types are ready to use.

In addition, COM Interop allows COM developers to access managed objects as easily as they
access other COM objects. Again, COM Interop provides a specialized utility (RegAsm.exe) that
exports the managed types into a type library and registers the managed component as a
traditional COM component.

At run time, the common language runtime marshals data between COM objects and managed
objects as needed.

This tutorial shows how to use C# to interoperate with COM objects.

COM Interop Part 2: C# Server Tutorial covers using a C# server with a C++ COM client. For an
overview of both tutorials, see COM Interop Tutorials.

C# uses .NET Framework facilities to perform COM Interop. C# has support for:

• Creating COM objects.

• Determining if a COM interface is implemented by an object.

• Calling methods on COM interfaces.

• Implementing objects and interfaces that can be called by COM clients.

The .NET Framework handles reference-counting issues with COM Interop so there is no need to
call or implement AddRef and Release.

This tutorial covers the following topics:

• Creating a COM Class Wrapper

• Declaring a COM coclass

• Creating a COM Object

• Declaring a COM Interface

• Using Casts Instead of QueryInterface

• Putting It All Together

Creating a COM Class Wrapper


For C# code to reference COM objects and interfaces, you need to include a .NET Framework
definition for the COM interfaces in your C# build. The easiest way to do this is to use TlbImp.exe

91
(Type Library Importer), a command-line tool included in the .NET Framework SDK. TlbImp
converts a COM type library into .NET Framework metadata — effectively creating a managed
wrapper that can be called from any managed language. .NET Framework metadata created with
TlbImp can be included in a C# build via the /R compiler option. If you are using the Visual Studio
development environment, you only need to add a reference to the COM type library and the
conversion is done for you automatically.

TlbImp performs the following conversions:

• COM coclasses are converted to C# classes with a parameterless constructor.

• COM structs are converted to C# structs with public fields.

A great way to check the output of TlbImp is to run the .NET Framework SDK command-line tool
Ildasm.exe (Microsoft Intermediate Language Disassembler) to view the result of the conversion.

Although TlbImp is the preferred method for converting COM definitions to C#, it is not always
possible to use it (for example, if there is no typelib for the COM definitions, or if TlbImp cannot
handle the definitions in the typelib). In these cases, the alternative is to manually define the COM
definitions in C# source code using C# attributes. Once you have created the C# source mapping,
you simply compile the C# source code to produce the managed wrapper.

The main attributes you need to understand to perform COM mapping are:

• ComImport - Marks a class as an externally implemented COM class.

• Guid – Used to specify a universally unique identifier (UUID) for a class or an interface.

• InterfaceType – specifies whether an interface derives from IUnknown or IDispatch.

• PreserveSig – specifies whether the native return value should be converted from an
HRESULT to a .NET Framework exception.

Each of these attributes is shown in the context of an actual example in this tutorial.

Declaring a COM coclass


COM coclasses are represented in C# as classes. These classes must have the ComImport
attribute associated with them. The following restrictions apply to these classes:

• The class must not inherit from any other class.

• The class must implement no interfaces.

• The class must also have a Guid attribute that sets the globally unique identifier (GUID)
for the class.

The following example declares a coclass in C#:

92
//
// declare FilgraphManager as a COM coclass
//
[ComImport, Guid("E436EBB3-524F-11CE-9F53-0020AF0BA770")]
class FilgraphManager
{
}
The C# compiler will add a parameterless constructor that you can call to create an instance of
the COM coclass.

Creating a COM Object


COM coclasses are represented in C# as classes with a parameterless constructor. Creating an
instance of this class using the new operator is the C# equivalent of calling CoCreateInstance.
Using the class defined above, it is simple to instantiate the class:

class MainClass
{
public static void Main()
{
//
// Create an instance of a COM coclass - calls
//
// CoCreateInstance(E436EBB3-524F-11CE-9F53-0020AF0BA770,
// NULL, CLSCTX_ALL,
// IID_IUnknown, &f)
//
// returns null on failure.
//
FilgraphManager f = new FilgraphManager();
}
}

Declaring a COM Interface


COM interfaces are represented in C# as interfaces with ComImport and Guid attributes. They
cannot include any interfaces in their base interface list, and they must declare the interface
member functions in the order that the methods appear in the COM interface.

COM interfaces declared in C# must include declarations for all members of their base interfaces
with the exception of members of IUnknown and IDispatch — the .NET Framework
automatically adds these. COM interfaces which derive from IDispatch must be marked with the
InterfaceType attribute.

93
When calling a COM interface method from C# code, the common language runtime must
marshal the parameters and return values to/from the COM object. For every .NET Framework
type, there is a default type that the common language runtime will use to marshal when
marshaling across a COM call. For example, the default marshaling for C# string values is to the
native type LPTSTR (pointer to TCHAR char buffer). You can override the default marshaling
using the MarshalAs attribute in the C# declaration of the COM interface.

In COM, a common way to return success or failure is to return an HRESULT and have an out
parameter marked as "retval" in MIDL for the real return value of the method. In C# (and the .NET
Framework), the standard way to indicate an error has occurred is to throw an exception.

By default, the .NET Framework provides an automatic mapping between the two styles of
exception handling for COM interface methods called by the .NET Framework.

• The return value changes to the signature of the parameter marked retval (void if the
method has no parameter marked as retval).
• The parameter marked as retval is left off of the argument list of the method.

Any non-success return value will cause a System.COMException exception to be thrown.

This example shows a COM interface declared in MIDL and the same interface declared in C#
(note that the methods use the COM error-handling approach).

Here is the original MIDL version of the interface:

[
odl,
uuid(56A868B1-0AD4-11CE-B03A-0020AF0BA770),
helpstring("IMediaControl interface"),
dual,
oleautomation
]
interface IMediaControl : IDispatch
{
[id(0x60020000)]
HRESULT Run();

[id(0x60020001)]
HRESULT Pause();

[id(0x60020002)]
HRESULT Stop();

94
[id(0x60020003)]
HRESULT GetState( [in] long msTimeout, [out] long* pfs);

[id(0x60020004)]
HRESULT RenderFile([in] BSTR strFilename);

[id(0x60020005)]
HRESULT AddSourceFilter( [in] BSTR strFilename, [out] IDispatch** ppUnk);

[id(0x60020006), propget]
HRESULT FilterCollection([out, retval] IDispatch** ppUnk);

[id(0x60020007), propget]
HRESULT RegFilterCollection([out, retval] IDispatch** ppUnk);

[id(0x60020008)]
HRESULT StopWhenReady();
};
Here is the C# equivalent of this interface:

using System.Runtime.InteropServices;

// Declare IMediaControl as a COM interface which


// derives from the IDispatch interface.
[Guid("56A868B1-0AD4-11CE-B03A-0020AF0BA770"),
InterfaceType(ComInterfaceType.InterfaceIsDual)]
interface IMediaControl // cannot list any base interfaces here
{
// Note that the members of IUnknown and Interface are NOT
// listed here
//
void Run();

void Pause();

void Stop();

void GetState( [In] int msTimeout, [Out] out int pfs);

void RenderFile(
[In, MarshalAs(UnmanagedType.BStr)] string strFilename);

95
void AddSourceFilter(
[In, MarshalAs(UnmanagedType.BStr)] string strFilename,
[Out, MarshalAs(UnmanagedType.Interface)] out object ppUnk);

[return : MarshalAs(UnmanagedType.Interface)]
object FilterCollection();

[return : MarshalAs(UnmanagedType.Interface)]
object RegFilterCollection();

void StopWhenReady();
}
Note how the C# interface has mapped the error-handling cases. If the COM method returns an
error, an exception will be raised on the C# side.

To prevent the translation of HRESULTs to COMExceptions, attach the PreserveSig(true)


attribute to the method in the C# declaration. For details, see PreserveSigAttribute Class.

Using Casts Instead of QueryInterface


A C# coclass is not very useful until you can access an interface that it implements. In C++ you
would navigate an object's interfaces using the QueryInterface method on the IUnknown
interface. In C# you can do the same thing by explicitly casting the COM object to the desired
COM interface. If the cast fails, then an invalid cast exception is thrown:

// Create an instance of a COM coclass:


FilgraphManager graphManager = new FilgraphManager();

// See if it supports the IMediaControl COM interface.


// Note that this will throw a System.InvalidCastException if
// the cast fails. This is equivalent to QueryInterface for
// COM objects:
IMediaControl mc = (IMediaControl) graphManager;

// Now you call a method on a COM interface:


mc.Run();

Putting It All Together


Here is a complete example that creates an AVI file viewer using C#. The program creates an
instance of a COM coclass, casts it to a COM interface, and then calls methods on the COM
interface.

96
The examples in this section represent two approaches:

• Example 1 Using TlbImp to create the .NET Framework class.

• Example 2 Writing C# code that manually does the COM mapping.

Example 1: Using TlbImp

This example shows you how to create an AVI viewer using TlbImp. The program reads an AVI
filename from the command line, creates an instance of the Quartz COM object, then uses the
methods RenderFile and Run to display the AVI file.

These are the steps to build the program:

• Run TlbImp over the TLB. The Media Player used in this example is contained in
Quartz.dll, which should be in your Windows system directory. Use the following
command to create the .NET Framework DLL:

tlbimp c:\winnt\system32\quartz.dll /out:QuartzTypeLib.dll


Note that the resulting DLL needs to be named QuartzTypeLib, so the .NET Framework
can load the containing types correctly when running.

• You can use the Ildasm tool to examine the resulting DLL. For example, to display the
contents of the file QuartzTypeLib.dll, use the following command:

Ildasm QuartzTypeLib.dll

• Build the program using the C# compiler option /R to include the QuartzTypeLib.dll file.

You can then use the program to display a movie (an example movie to try is Clock.avi, which
resides in your Windows directory).

// interop1.cs
// compile with: /R:QuartzTypeLib.dll
using System;
class MainClass
{
/************************************************************
Abstract: This method collects the file name of an AVI to
show then creates an instance of the Quartz COM object.
To show the AVI, the program calls RenderFile and Run on
IMediaControl. Quartz uses its own thread and window to
display the AVI.The main thread blocks on a ReadLine until

97
the user presses ENTER.
Input Parameters: the location of the AVI file it is
going to display
Returns: void
**************************************************************/
public static void Main(string[] args)
{
// Check to see if the user passed in a filename
if (args.Length != 1)
{
DisplayUsage();
return;
}

if (args[0] == "/?")
{
DisplayUsage();
return;
}

string filename = args[0];

// Check to see if the file exists


if (!System.IO.File.Exists(filename))
{
Console.WriteLine("File " + filename + " not found.");
DisplayUsage();
return;
}

// Create instance of Quartz


// (Calls CoCreateInstance(E436EBB3-524F-11CE-9F53-0020AF0BA770,
// NULL, CLSCTX_ALL, IID_IUnknown, &graphManager).):

try
{
QuartzTypeLib.FilgraphManager graphManager =
new QuartzTypeLib.FilgraphManager();

// QueryInterface for the IMediaControl interface:

98
QuartzTypeLib.IMediaControl mc =
(QuartzTypeLib.IMediaControl)graphManager;

// Call some methods on a COM interface


// Pass in file to RenderFile method on COM object.
mc.RenderFile(filename);

// Show file.
mc.Run();
}
catch(Exception ex)
{
Console.WriteLine("Unexpected COM exception: " + ex.Message);
}

// Wait for completion.


Console.WriteLine("Press Enter to continue.");
Console.ReadLine();
}

private static void DisplayUsage()


{
// User did not provide enough parameters.
// Display usage:
Console.WriteLine("VideoPlayer: Plays AVI files.");
Console.WriteLine("Usage: VIDEOPLAYER.EXE filename");
Console.WriteLine("where filename is the full path and");
Console.WriteLine("file name of the AVI to display.");
}
}

Sample Run

To display the example movie, Clock.avi, use the following command:

interop1 %windir%\clock.avi
This will display the movie on your screen after you press ENTER.

Example 2: The C# Code Approach

99
This example uses the same Main method as Example 1, but instead of running TlbImp, it simply
maps the Media Player COM object using C#.

// interop2.cs
using System;
using System.Runtime.InteropServices;

namespace QuartzTypeLib
{
// Declare IMediaControl as a COM interface which
// derives from IDispatch interface:
[Guid("56A868B1-0AD4-11CE-B03A-0020AF0BA770"),
InterfaceType(ComInterfaceType.InterfaceIsDual)]
interface IMediaControl // Cannot list any base interfaces here
{
// Note that IUnknown Interface members are NOT listed here:

void Run();
void Pause();

void Stop();

void GetState( [In] int msTimeout, [Out] out int pfs);

void RenderFile(
[In, MarshalAs(UnmanagedType.BStr)] string strFilename);

void AddSourceFilter(
[In, MarshalAs(UnmanagedType.BStr)] string strFilename,
[Out, MarshalAs(UnmanagedType.Interface)]
out object ppUnk);

[return: MarshalAs(UnmanagedType.Interface)]
object FilterCollection();

[return: MarshalAs(UnmanagedType.Interface)]
object RegFilterCollection();

void StopWhenReady();
}

100
// Declare FilgraphManager as a COM coclass:
[ComImport, Guid("E436EBB3-524F-11CE-9F53-0020AF0BA770")]
class FilgraphManager // Cannot have a base class or
// interface list here.
{
// Cannot have any members here
// NOTE that the C# compiler will add a default constructor
// for you (no parameters).
}
}

class MainClass
{
/**********************************************************
Abstract: This method collects the file name of an AVI to
show then creates an instance of the Quartz COM object.
To show the AVI, the program calls RenderFile and Run on
IMediaControl. Quartz uses its own thread and window to
display the AVI.The main thread blocks on a ReadLine until
the user presses ENTER.
Input Parameters: the location of the AVI file it is
going to display
Returns: void
*************************************************************/

public static void Main(string[] args)


{
// Check to see if the user passed in a filename:
if (args.Length != 1)
{
DisplayUsage();
return;
}

if (args[0] == "/?")
{
DisplayUsage();
return;
}

101
String filename = args[0];

// Check to see if the file exists


if (!System.IO.File.Exists(filename))
{
Console.WriteLine("File " + filename + " not found.");
DisplayUsage();
return;
}

// Create instance of Quartz


// (Calls CoCreateInstance(E436EBB3-524F-11CE-9F53-0020AF0BA770,
// NULL, CLSCTX_ALL, IID_IUnknown,
// &graphManager).):
try
{
QuartzTypeLib.FilgraphManager graphManager =
new QuartzTypeLib.FilgraphManager();

// QueryInterface for the IMediaControl interface:


QuartzTypeLib.IMediaControl mc =
(QuartzTypeLib.IMediaControl)graphManager;

// Call some methods on a COM interface.


// Pass in file to RenderFile method on COM object.
mc.RenderFile(filename);

// Show file.
mc.Run();
}
catch(Exception ex)
{
Console.WriteLine("Unexpected COM exception: " + ex.Message);
}
// Wait for completion.
Console.WriteLine("Press Enter to continue.");
Console.ReadLine();
}

private static void DisplayUsage()

102
{
// User did not provide enough parameters.
// Display usage.
Console.WriteLine("VideoPlayer: Plays AVI files.");
Console.WriteLine("Usage: VIDEOPLAYER.EXE filename");
Console.WriteLine("where filename is the full path and");
Console.WriteLine("file name of the AVI to display.");
}
}

Sample Run

To display the example movie, Clock.avi, use the following command:

interop2 %windir%\clock.avi
This will display the movie on your screen after you press ENTER.

COM Interop Part 2: C# Server Tutorial

COM Interop allows COM developers to access managed code as easily as they access other
COM objects. This tutorial demonstrates using a C# server with a C++ COM client. It also
explains the following activities:

• How to create the C# server

• How to create the COM client

The tutorial also briefly demonstrates the marshaling that is automatically applied between
managed and unmanaged components.

COM Interop Part 1: C# Client Tutorial shows the fundamentals of using C# to interoperate with
COM objects and is a prerequisite for this tutorial. For an overview of both tutorials, see COM
Interop Tutorials.

This tutorial demonstrates the following activities to create the C# server:

• How to use the Guid attribute on interfaces and classes to expose them as COM objects
and how to generate a globally unique identifier (GUID) for the Guid attribute.
• How to use RegAsm to register a .NET Framework program for use by COM clients and
create a type library (.tlb file) from a .NET Framework program.

The tutorial also demonstrates the following activities to create the COM client:

103
• How to export managed servers and how to use them to create COM objects.

• How to import the .tlb file, generated by RegAsm, into the COM client and how to use
CoCreateInstance to create an instance of a .NET Framework coclass.

Note To create a GUID for interfaces and coclasses that you export to COM clients, use
the tool Guidgen.exe, shipped as part of Visual Studio. Guidgen allows you to choose the
format in which the GUID is expressed so you don't have to retype it. For more
information on Guidgen, see the Knowledge Base article Q168318 "XADM: Guidgen.exe
Available Only for Intel Platforms." KB articles are available in the MSDN Library and on
the Web at http://support.microsoft.com.

Example

This example consists of two files:

• A C# file, CSharpServer.cs, that creates the CSharpServer.dll file. The .dll is used to
create the file CSharpServer.tlb.
• A C++ file, COMClient.cpp, that creates the executable client, COMClient.exe.

File 1: CSharpServer.cs

// CSharpServer.cs
// compile with: /target:library
// post-build command: regasm CSharpServer.dll /tlb:CSharpServer.tlb

using System;
using System.Runtime.InteropServices;
namespace CSharpServer
{
// Since the .NET Framework interface and coclass have to behave as
// COM objects, we have to give them guids.
[Guid("DBE0E8C4-1C61-41f3-B6A4-4E2F353D3D05")]
public interface IManagedInterface
{
int PrintHi(string name);
}

[Guid("C6659361-1625-4746-931C-36014B146679")]
public class InterfaceImplementation : IManagedInterface
{

104
public int PrintHi(string name)
{
Console.WriteLine("Hello, {0}!", name);
return 33;
}
}
}

File 2: COMClient.cpp

// COMClient.cpp
// Build with "cl COMClient.cpp"
// arguments: friend

#include <windows.h>
#include <stdio.h>

#pragma warning (disable: 4278)

// To use managed-code servers like the C# server,


// we have to import the common language runtime:
#import <mscorlib.tlb> raw_interfaces_only

// For simplicity, we ignore the server namespace and use named guids:
#if defined (USINGPROJECTSYSTEM)
#import "..\RegisterCSharpServerAndExportTLB\CSharpServer.tlb" no_namespace named_guids
#else // Compiling from the command line, all files in the same directory
#import "CSharpServer.tlb" no_namespace named_guids
#endif
int main(int argc, char* argv[])
{
IManagedInterface *cpi = NULL;
int retval = 1;

// Initialize COM and create an instance of the InterfaceImplementation class:


CoInitialize(NULL);
HRESULT hr = CoCreateInstance(CLSID_InterfaceImplementation,
NULL, CLSCTX_INPROC_SERVER,
IID_IManagedInterface, reinterpret_cast<void**>(&cpi));

105
if (FAILED(hr))
{
printf("Couldn't create the instance!... 0x%x\n", hr);
}
else
{
if (argc > 1)
{
printf("Calling function.\n");
fflush(stdout);
// The variable cpi now holds an interface pointer
// to the managed interface.
// If you are on an OS that uses ASCII characters at the
// command prompt, notice that the ASCII characters are
// automatically marshaled to Unicode for the C# code.
if (cpi->PrintHi(argv[1]) == 33)
retval = 0;
printf("Returned from function.\n");
}
else
printf ("Usage: COMClient <name>\n");
cpi->Release();
cpi = NULL;
}

// Be a good citizen and clean up COM:


CoUninitialize();
return retval;
}

Output

The executable client can be invoked with the command line: COMClient <name>, where <name>
is any string you want to use, for example, COMClient friend.

Calling function.
Hello, friend!
Returned from function.
In the sample IDE project, set the Command Line Arguments property in the project's Property
Pages to the desired string (for example, "friend").

106
Attributes Tutorial

This tutorial shows how to create custom attribute classes, use them in code, and query them
through reflection.

Attributes provide a powerful method of associating declarative information with C# code (types,
methods, properties, and so forth). Once associated with a program entity, the attribute can be
queried at run time and used in any number of ways.

Example usage of attributes includes:

• Associating help documentation with program entities (through a Help attribute).

• Associating value editors to a specific type in a GUI framework (through a ValueEditor


attribute).

In addition to a complete example, this tutorial includes the following topics:

• Declaring an Attribute Class

The first thing you need to be able to do is declare an attribute.

• Using an Attribute Class

Once the attribute has been created, you then associate the attribute with a particular
program element.

• Accessing Attributes Through Reflection

Once the attribute has been associated with a program element, you use reflection to
query its existence and its value.

Declaring an Attribute Class


Declaring an attribute in C# is simple — it takes the form of a class declaration that inherits from
System.Attribute and has been marked with the AttributeUsage attribute as shown below:

using System;
[AttributeUsage(AttributeTargets.All)]
public class HelpAttribute : System.Attribute
{
public readonly string Url;

public string Topic // Topic is a named parameter

107
{
get
{
return topic;
}
set
{

topic = value;
}
}

public HelpAttribute(string url) // url is a positional parameter


{
this.Url = url;
}

private string topic;


}

Code Discussion

• The attribute AttributeUsage specifies the language elements to which the attribute can
be applied.
• Attributes classes are public classes derived from System.Attribute that have at least
one public constructor.
• Attribute classes have two types of parameters:

• Positional parameters must be specified every time the attribute is used.


Positional parameters are specified as constructor arguments to the attribute class. In
the example above, url is a positional parameter.
• Named parameters are optional. If they are specified when the attribute is used,
the name of the parameter must be used. Named parameters are defined by having a
nonstatic field or property. In the example above, Topic is a named parameter.
• Attribute parameters are restricted to constant values of the following types:

• Simple types (bool, byte, char, short, int, long, float, and double)

• string

• System.Type

• enums

108
• object (The argument to an attribute parameter of type object must be a constant
value of one of the above types.)
• One-dimensional arrays of any of the above types

Parameters for the AttributeUsage Attribute

The attribute AttributeUsage provides the underlying mechanism by which attributes are
declared.

AttributeUsage has one positional parameter:

• AllowOn, which specifies the program elements that the attribute can be assigned to
(class, method, property, parameter, and so on). Valid values for this parameter can be
found in the System.Attributes.AttributeTargets enumeration in the .NET Framework.
The default value for this parameter is all program elements (AttributeElements.All).

AttributeUsage has one named parameter:

• AllowMultiple, a Boolean value that indicates whether multiple attributes can be


specified for one program element. The default value for this parameter is False.

Using an Attribute Class


Here's a simple example of using the attribute declared in the previous section:

[HelpAttribute("http://localhost/MyClassInfo")]
class MyClass
{
}
In this example, the HelpAttribute attribute is associated with MyClass.

Note By convention, all attribute names end with the word "Attribute" to distinguish them from
other items in the .NET Framework. However, you do not need to specify the attribute suffix when
using attributes in code. For example, you can specify HelpAttribute as follows:
[Help("http://localhost/MyClassInfo")] // [Help] == [HelpAttribute]
class MyClass
{
}

Accessing Attributes Through Reflection


Once attributes have been associated with program elements, reflection can be used to query
their existence and values. The main reflection methods to query attributes are contained in the

109
System.Reflection.MemberInfo class (GetCustomAttributes family of methods). The following
example demonstrates the basic way of using reflection to get access to attributes:

class MainClass
{
public static void Main()
{
System.Reflection.MemberInfo info = typeof(MyClass);
object[] attributes = info.GetCustomAttributes(true);
for (int i = 0; i < attributes.Length; i ++)
{
System.Console.WriteLine(attributes[i]);
}
}
}

Example

The following is a complete example where all pieces are brought together.

// AttributesTutorial.cs
// This example shows the use of class and method attributes.

using System;
using System.Reflection;
using System.Collections;

// The IsTested class is a user-defined custom attribute class.


// It can be applied to any declaration including
// - types (struct, class, enum, delegate)
// - members (methods, fields, events, properties, indexers)
// It is used with no arguments.
public class IsTestedAttribute : Attribute
{
public override string ToString()
{
return "Is Tested";
}
}

// The AuthorAttribute class is a user-defined attribute class.

110
// It can be applied to classes and struct declarations only.
// It takes one unnamed string argument (the author's name).
// It has one optional named argument Version, which is of type int.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class AuthorAttribute : Attribute
{
// This constructor specifies the unnamed arguments to the attribute class.
public AuthorAttribute(string name)
{
this.name = name;
this.version = 0;
}

// This property is readonly (it has no set accessor)


// so it cannot be used as a named argument to this attribute.
public string Name
{
get
{
return name;
}
}

// This property is read-write (it has a set accessor)


// so it can be used as a named argument when using this
// class as an attribute class.
public int Version
{
get
{
return version;
}
set
{
version = value;
}
}

public override string ToString()


{

111
string value = "Author : " + Name;
if (version != 0)
{
value += " Version : " + Version.ToString();
}
return value;
}

private string name;


private int version;
}

// Here you attach the AuthorAttribute user-defined custom attribute to


// the Account class. The unnamed string argument is passed to the
// AuthorAttribute class's constructor when creating the attributes.
[Author("Joe Programmer")]
class Account
{
// Attach the IsTestedAttribute custom attribute to this method.
[IsTested]
public void AddOrder(Order orderToAdd)
{
orders.Add(orderToAdd);
}

private ArrayList orders = new ArrayList();


}

// Attach the AuthorAttribute and IsTestedAttribute custom attributes


// to this class.
// Note the use of the 'Version' named argument to the AuthorAttribute.
[Author("Jane Programmer", Version = 2), IsTested()]
class Order
{
// add stuff here ...
}

class MainClass
{
private static bool IsMemberTested(MemberInfo member)

112
{
foreach (object attribute in member.GetCustomAttributes(true))
{
if (attribute is IsTestedAttribute)
{
return true;
}
}
return false;
}

private static void DumpAttributes(MemberInfo member)


{
Console.WriteLine("Attributes for : " + member.Name);
foreach (object attribute in member.GetCustomAttributes(true))
{
Console.WriteLine(attribute);
}
}

public static void Main()


{
// display attributes for Account class
DumpAttributes(typeof(Account));

// display list of tested members


foreach (MethodInfo method in (typeof(Account)).GetMethods())
{
if (IsMemberTested(method))
{
Console.WriteLine("Member {0} is tested!", method.Name);
}
else
{
Console.WriteLine("Member {0} is NOT tested!", method.Name);
}
}
Console.WriteLine();

// display attributes for Order class

113
DumpAttributes(typeof(Order));

// display attributes for methods on the Order class


foreach (MethodInfo method in (typeof(Order)).GetMethods())
{
if (IsMemberTested(method))
{
Console.WriteLine("Member {0} is tested!", method.Name);
}
else
{
Console.WriteLine("Member {0} is NOT tested!", method.Name);
}
}
Console.WriteLine();
}
}

Output

Attributes for : Account


Author : Joe Programmer
Member GetHashCode is NOT tested!
Member Equals is NOT tested!
Member ToString is NOT tested!
Member AddOrder is tested!
Member GetType is NOT tested!

Attributes for : Order


Author : Jane Programmer Version : 2
Is Tested
Member GetHashCode is NOT tested!
Member Equals is NOT tested!
Member ToString is NOT tested!
Member GetType is NOT tested!

Security Tutorial

This tutorial discusses .NET Framework security and shows two ways to modify security
permissions in C#: imperative and declarative security.

114
Most application and component developers should not need to do anything special in order to
work with the .NET Framework security system and benefit from the safety protection it provides.

One exception that requires more in-depth knowledge and special consideration of the security
system is secure libraries. This code represents the boundary between secure managed code
and unrestricted code, such as native code (that is outside the ability of the .NET Framework
security infrastructure to enforce). These libraries typically must be highly trusted to work, and are
the one place in managed code where a programming error can potentially expose a security
vulnerability. Code access security can't eliminate the potential for human error, but compared to
the much larger volume of application code that uses a few secure libraries, the amount of code
that requires close scrutiny is dramatically reduced.

Examples
The tutorial includes the following examples:

• Example 1: Imperative Security

• Example 2: Declarative Security

• Example 3: Suppressing Security

Security
The .NET Framework security protects your code and data from being misused or damaged by
other code by enforcing security restrictions on managed code. When a .NET Framework
application requests permission, the security policy established by the administrator grants the
permission or refuses to run the code. Trust is based on evidence about the code such as a
digital signature, where the code comes from, and so forth. Once granted, security enforces
permissions that control what code is (and by not being granted, what code is not) allowed to do.

Permissions
The .NET Framework security allows code to use protected resources only if it has "permission"
to do so. To express this, the .NET Framework uses the concept of permissions, which represent
the right for code to access protected resources. Code requests the permissions it needs, and the
security policy applied by the .NET Framework determines which permissions the code is actually
granted.

The .NET Framework provides code access permission classes, each of which encapsulates the
ability to access a particular resource. You use these permissions to indicate to the .NET
Framework what your code needs to be allowed to do and to indicate what your code's callers
must be authorized to do. Policy also uses these objects to determine what permissions to grant
to code.

115
Policy
Enforcement of security policy is what makes .NET Framework managed code safe. Every
assembly that loads is subject to security policy that grants code permissions based on trust, with
trust based on evidence about the code. See the .NET Framework documentation in the Reading
List for information on administering security policy.

Demanding Security Permissions in C#


There are two ways to demand security permissions in C#:

• Imperatively: Using calls to permission classes in the .NET Framework

• Declaratively: Using security permission attributes

The following two examples demonstrate these two approaches. For more information on
demanding security permissions, see Demands.

Example 1: Imperative Security

The following is an example of using .NET Framework calls to deny the UnmanagedCode
permission.

// ImperativeSecurity.cs
using System;
using System.Security;
using System.Security.Permissions;
using System.Runtime.InteropServices;

class NativeMethods
{
// This is a call to unmanaged code. Executing this method requires
// the UnmanagedCode security permission. Without this permission
// an attempt to call this method will throw a SecurityException:
[DllImport("msvcrt.dll")]
public static extern int puts(string str);
[DllImport("msvcrt.dll")]
internal static extern int _flushall();
}

class MainClass
{
private static void CallUnmanagedCodeWithoutPermission()
{

116
// Create a security permission object to describe the
// UnmanagedCode permission:
SecurityPermission perm =
new SecurityPermission(SecurityPermissionFlag.UnmanagedCode);

// Deny the UnmanagedCode from our current set of permissions.


// Any method that is called on this thread until this method
// returns will be denied access to unmanaged code.
// Even though the CallUnmanagedCodeWithPermission method
// is called from a stack frame that already
// calls Assert for unmanaged code, you still cannot call native
// code. Because you use Deny here, the permission gets
// overwritten.
perm.Deny();

try
{
Console.WriteLine("Attempting to call unmanaged code without permission.");
NativeMethods.puts("Hello World!");
NativeMethods._flushall();
Console.WriteLine("Called unmanaged code without permission. Whoops!");
}
catch (SecurityException)
{
Console.WriteLine("Caught Security Exception attempting to call unmanaged code.");
}
}

private static void CallUnmanagedCodeWithPermission()


{
// Create a security permission object to describe the
// UnmanagedCode permission:
SecurityPermission perm =
new SecurityPermission(SecurityPermissionFlag.UnmanagedCode);

// Check that you have permission to access unmanaged code.


// If you don't have permission to access unmanaged code, then
// this call will throw a SecurityException.
// Even though the CallUnmanagedCodeWithPermission method
// is called from a stack frame that already

117
// calls Assert for unmanaged code, you still cannot call native
// code. Because you use Deny here, the permission gets
// overwritten.
perm.Assert();

try
{
Console.WriteLine("Attempting to call unmanaged code with permission.");
NativeMethods.puts("Hello World!");
NativeMethods._flushall();
Console.WriteLine("Called unmanaged code with permission.");
}
catch (SecurityException)
{
Console.WriteLine("Caught Security Exception attempting to call unmanaged code.
Whoops!");
}
}

public static void Main()


{
// The method itself will call the security permission Deny
// for unmanaged code, which will override the Assert permission
// in this stack frame.
SecurityPermission perm = new
SecurityPermission(SecurityPermissionFlag.UnmanagedCode);
perm.Assert();
CallUnmanagedCodeWithoutPermission();

// The method itself will call the security permission Assert


// for unmanaged code, which will override the Deny permission in
// this stack frame.
perm.Deny();
CallUnmanagedCodeWithPermission();
}
}

Output

Attempting to call unmanaged code without permission.

118
Caught Security Exception attempting to call unmanaged code.
Attempting to call unmanaged code with permission.
Hello World!
Called unmanaged code with permission.

Example 2: Declarative Security

This is the same example using attributes for the security permissions.

// DeclarativeSecurity.cs
using System;
using System.Security;
using System.Security.Permissions;
using System.Runtime.InteropServices;

class NativeMethods
{
// This is a call to unmanaged code. Executing this method requires
// the UnmanagedCode security permission. Without this permission,
// an attempt to call this method will throw a SecurityException:
[DllImport("msvcrt.dll")]
public static extern int puts(string str);
[DllImport("msvcrt.dll")]
internal static extern int _flushall();
}

class MainClass
{
// The security permission attached to this method will deny the
// UnmanagedCode permission from the current set of permissions for
// the duration of the call to this method:
// Even though the CallUnmanagedCodeWithoutPermission method is
// called from a stack frame that already calls
// Assert for unmanaged code, you still cannot call native code.
// Because this function is attached with the Deny permission for
// unmanaged code, the permission gets overwritten.
[SecurityPermission(SecurityAction.Deny, Flags =
SecurityPermissionFlag.UnmanagedCode)]
private static void CallUnmanagedCodeWithoutPermission()
{
try

119
{
Console.WriteLine("Attempting to call unmanaged code without permission.");
NativeMethods.puts("Hello World!");
NativeMethods._flushall();
Console.WriteLine("Called unmanaged code without permission. Whoops!");
}
catch (SecurityException)
{
Console.WriteLine("Caught Security Exception attempting to call unmanaged code.");
}
}

// The security permission attached to this method will force a


// runtime check for the unmanaged code permission whenever
// this method is called. If the caller does not have unmanaged code
// permission, then the call will generate a Security Exception.
// Even though the CallUnmanagedCodeWithPermission method is called
// from a stack frame that already calls
// Deny for unmanaged code, it will not prevent you from calling
// native code. Because this method is attached with the Assert
// permission for unmanaged code, the permission gets overwritten.
[SecurityPermission(SecurityAction.Assert, Flags =
SecurityPermissionFlag.UnmanagedCode)]
private static void CallUnmanagedCodeWithPermission()
{
try
{
Console.WriteLine("Attempting to call unmanaged code with permission.");
NativeMethods.puts("Hello World!");
NativeMethods._flushall();
Console.WriteLine("Called unmanaged code with permission.");
}
catch (SecurityException)
{
Console.WriteLine("Caught Security Exception attempting to call unmanaged code.
Whoops!");
}
}

public static void Main()

120
{
SecurityPermission perm = new
SecurityPermission(SecurityPermissionFlag.UnmanagedCode);

// The method itself is attached with the security permission


// Deny for unmanaged code, which will override
// the Assert permission in this stack frame.
perm.Assert();
CallUnmanagedCodeWithoutPermission();

// The method itself is attached with the security permission


// Assert for unmanaged code, which will override the Deny
// permission in this stack frame.
perm.Deny();
CallUnmanagedCodeWithPermission();
}
}

Output

Attempting to call unmanaged code without permission.


Caught Security Exception attempting to call unmanaged code.
Attempting to call unmanaged code with permission.
Hello World!
Called unmanaged code with permission.

Security and Performance


The .NET Framework security system prevents malicious code downloaded from the network
from damaging your computer system. However, these security checks are not without cost, even
if your code never throws a SecurityException.

Normally the common language runtime verifies that the caller of an unmanaged method has
unmanaged code access permission at run time for every call to the unmanaged method. This
can be very expensive for applications that make many calls to unmanaged code. The
SuppressUnmanagedCodeSecurityAttribute changes this default behavior. When an
unmanaged method is declared with this attribute, the security demand is checked when code
that calls the method is loaded into the common language runtime.

Security Note When using the SuppressUnmanagedCodeSecurityAttribute, you should take


extra care to ensure that you are not introducing a security hole. For example, the developer

121
needs to verify that he/she is using the unmanaged API safely and that callers cannot influence or
abuse the call. Alternatively, the developer can add an appropriate demand to ensure that all
callers have the appropriate permissions. For example, if a call was made into native code to
access a file (to take advantage of structured storage such as extended file properties, and so
forth) and the unmanaged code demand was suppressed, then a file IO demand should be made
explicitly to ensure that the code cannot be misused.

Example 3: Optimizing Unmanaged Calls

In this example, the check for the unmanaged code permission is executed once at load time,
rather than upon every call to the unmanaged method. If the unmanaged method is called
multiple times, this could yield a significant performance gain.

// SuppressSecurity.cs
using System;
using System.Security;
using System.Security.Permissions;
using System.Runtime.InteropServices;

class NativeMethods
{
// This is a call to unmanaged code. Executing this method requires
// the UnmanagedCode security permission. Without this permission,
// an attempt to call this method will throw a SecurityException:
/* NOTE: The SuppressUnmanagedCodeSecurityAttribute disables the
check for the UnmanagedCode permission at runtime. Be Careful! */
[SuppressUnmanagedCodeSecurityAttribute()]
[DllImport("msvcrt.dll")]
internal static extern int puts(string str);
[SuppressUnmanagedCodeSecurityAttribute()]
[DllImport("msvcrt.dll")]
internal static extern int _flushall();
}

class MainClass
{
// The security permission attached to this method will remove the
// UnmanagedCode permission from the current set of permissions for
// the duration of the call to this method.
// Even though the CallUnmanagedCodeWithoutPermission method is
// called from a stack frame that already calls

122
// Assert for unmanaged code, you still cannot call native code.
// Because this method is attached with the Deny permission for
// unmanaged code, the permission gets overwritten. However, because
// you are using SuppressUnmanagedCodeSecurityAttribute here, you can
// still call the unmanaged methods successfully.
// The code should use other security checks to ensure that you don't
// incur a security hole.
[SecurityPermission(SecurityAction.Deny, Flags =
SecurityPermissionFlag.UnmanagedCode)]
private static void CallUnmanagedCodeWithoutPermission()
{
try
{
// The UnmanagedCode security check is disbled on the call
// below. However, the unmanaged call only displays UI. The
// security will be ensured by only allowing the unmanaged
// call if there is a UI permission.
UIPermission uiPermission =
new UIPermission(PermissionState.Unrestricted);
uiPermission.Demand();

Console.WriteLine("Attempting to call unmanaged code without UnmanagedCode


permission.");
NativeMethods.puts("Hello World!");
NativeMethods._flushall();
Console.WriteLine("Called unmanaged code without UnmanagedCode permission.");
}
catch (SecurityException)
{
Console.WriteLine("Caught Security Exception attempting to call unmanaged code.");
}
}

// The security permission attached to this method will add the


// UnmanagedCode permission to the current set of permissions for the
// duration of the call to this method.
// Even though the CallUnmanagedCodeWithPermission method is called
// from a stack frame that already calls
// Deny for unmanaged code, it will not prevent you from calling
// native code. Because this method is attached with the Assert

123
// permission for unmanaged code, the permission gets overwritten.
// Because you are using SuppressUnmanagedCodeSecurityAttribute here,
// you can call the unmanaged methods successfully.
// The SuppressUnmanagedCodeSecurityAttribute will let you succeed,
// even if you don't have a permission.
[SecurityPermission(SecurityAction.Assert, Flags =
SecurityPermissionFlag.UnmanagedCode)]
private static void CallUnmanagedCodeWithPermission()
{
try
{
Console.WriteLine("Attempting to call unmanaged code with permission.");
NativeMethods.puts("Hello World!");
NativeMethods._flushall();
Console.WriteLine("Called unmanaged code with permission.");
}
catch (SecurityException)
{
Console.WriteLine("Caught Security Exception attempting to call unmanaged code.
Whoops!");
}
}

public static void Main()


{
SecurityPermission perm = new
SecurityPermission(SecurityPermissionFlag.UnmanagedCode);

// The method itself is attached with the security permission Deny


// for unmanaged code, which will override the Assert permission in
// this stack frame. However, because you are using
// SuppressUnmanagedCodeSecurityAttribute, you can still call the
// unmanaged methods successfully.
// The code should use other security checks to ensure that you
// don't incur a security hole.
perm.Assert();
CallUnmanagedCodeWithoutPermission();

// The method itself is attached with the security permission


// Assert for unmanaged code, which will override the Deny

124
// permission in this stack frame. Because you are using
// SuppressUnmanagedCodeSecurityAttribute, you can call the
// unmanaged methods successfully.
// The SuppressUnmanagedCodeSecurityAttribute will let you succeed,
// even if you don't have a permission.
perm.Deny();
CallUnmanagedCodeWithPermission();
}
}

Output

Attempting to call unmanaged code without UnmanagedCode permission.


Hello World!
Called unmanaged code without UnmanagedCode permission.
Attempting to call unmanaged code with permission.
Hello World!
Called unmanaged code with permission.

Code Discussion

Note that the above example allows both unmanaged calls to succeed even though the first call
doesn't have UnmanagedCode permission. When using the
SuppressUnmanagedCodeSecurityAttribute, you should use other security checks to ensure
that you don't incur a security hole. In the above example, this is done by adding the Demand for
the UIPermission:

uiPermission.Demand();
before the unmanaged call, which ensures that the caller has permission to display UI.

Threading Tutorial

The advantage of threading is the ability to create applications that use more than one thread of
execution. For example, a process can have a user interface thread that manages interactions
with the user and worker threads that perform other tasks while the user interface thread waits for
user input.

This tutorial demonstrates various thread activities:

• Creating and executing a thread

• Synchronization of threads

125
• Interaction between threads

• Using a thread pool

• Using a mutex object to protect a shared resource

This tutorial contains the following examples:

• Example 1: Creating, starting, and interacting between threads

• Example 2: Synchronizing two threads: a producer and a consumer

• Example 3: Using a thread pool

• Example 4: Using the Mutex object

Example 1: Creating, starting, and interacting between threads

This example demonstrates how to create and start a thread, and shows the interaction between
two threads running simultaneously within the same process. Note that you don't have to stop or
free the thread. This is done automatically by the .NET Framework common language runtime.

The program begins by creating an object of type Alpha (oAlpha) and a thread (oThread) that
references the Beta method of the Alpha class. The thread is then started. The IsAlive property of
the thread allows the program to wait until the thread is initialized (created, allocated, and so on).
The main thread is accessed through Thread, and the Sleep method tells the thread to give up its
time slice and stop executing for a certain amount of milliseconds. The oThread is then stopped
and joined. Joining a thread makes the main thread wait for it to die or for a specified time to
expire (for more details, see Thread.Join Method). Finally, the program attempts to restart
oThread, but fails because a thread cannot be restarted after it is stopped (aborted). For
information on temporary cessation of execution, see Suspending Thread Execution.

// StopJoin.cs
using System;
using System.Threading;

public class Alpha


{

// This method that will be called when the thread is started


public void Beta()
{
while (true)
{
Console.WriteLine("Alpha.Beta is running in its own thread.");

126
}
}
};

public class Simple


{
public static int Main()
{
Console.WriteLine("Thread Start/Stop/Join Sample");

Alpha oAlpha = new Alpha();

// Create the thread object, passing in the Alpha.Beta method


// via a ThreadStart delegate. This does not start the thread.
Thread oThread = new Thread(new ThreadStart(oAlpha.Beta));

// Start the thread


oThread.Start();

// Spin for a while waiting for the started thread to become


// alive:
while (!oThread.IsAlive);

// Put the Main thread to sleep for 1 millisecond to allow oThread


// to do some work:
Thread.Sleep(1);

// Request that oThread be stopped


oThread.Abort();

// Wait until oThread finishes. Join also has overloads


// that take a millisecond interval or a TimeSpan object.
oThread.Join();

Console.WriteLine();
Console.WriteLine("Alpha.Beta has finished");

try
{
Console.WriteLine("Try to restart the Alpha.Beta thread");

127
oThread.Start();
}
catch (ThreadStateException)
{
Console.Write("ThreadStateException trying to restart Alpha.Beta. ");
Console.WriteLine("Expected since aborted threads cannot be restarted.");
}
return 0;
}
}

Example Output

Thread Start/Stop/Join Sample


Alpha.Beta is running in its own thread.
Alpha.Beta is running in its own thread.
Alpha.Beta is running in its own thread.
...
...
Alpha.Beta has finished
Try to restart the Alpha.Beta thread
ThreadStateException trying to restart Alpha.Beta. Expected since aborted threads cannot be
restarted.

Example 2: Synchronizing two threads: a producer and a consumer

The following example shows how synchronization can be accomplished using the C# lock
keyword and the Pulse method of the Monitor object. The Pulse method notifies a thread in the
waiting queue of a change in the object's state (for more details on pulses, see the Monitor.Pulse
Method).

The example creates a Cell object that has two methods: ReadFromCell and WriteToCell. Two
other objects are created from classes CellProd and CellCons; these objects both have a method
ThreadRun whose job is to call ReadFromCell and WriteToCell. Synchronization is accomplished
by waiting for "pulses" from the Monitor object, which come in order. That is, first an item is
produced (the consumer at this point is waiting for a pulse), then a pulse occurs, then the
consumer consumes the production (while the producer is waiting for a pulse), and so on.

// MonitorSample.cs
// This example shows use of the following methods of the C# lock keyword
// and the Monitor class
// in threads:

128
// Monitor.Pulse(Object)
// Monitor.Wait(Object)
using System;
using System.Threading;

public class MonitorSample


{
public static void Main(String[] args)
{
int result = 0; // Result initialized to say there is no error
Cell cell = new Cell( );

CellProd prod = new CellProd(cell, 20); // Use cell for storage,


// produce 20 items
CellCons cons = new CellCons(cell, 20); // Use cell for storage,
// consume 20 items

Thread producer = new Thread(new ThreadStart(prod.ThreadRun));


Thread consumer = new Thread(new ThreadStart(cons.ThreadRun));
// Threads producer and consumer have been created,
// but not started at this point.

try
{
producer.Start( );
consumer.Start( );

producer.Join( ); // Join both threads with no timeout


// Run both until done.
consumer.Join( );
// threads producer and consumer have finished at this point.
}
catch (ThreadStateException e)
{
Console.WriteLine(e); // Display text of exception
result = 1; // Result says there was an error
}
catch (ThreadInterruptedException e)
{
Console.WriteLine(e); // This exception means that the thread

129
// was interrupted during a Wait
result = 1; // Result says there was an error
}
// Even though Main returns void, this provides a return code to
// the parent process.
Environment.ExitCode = result;
}
}

public class CellProd


{
Cell cell; // Field to hold cell object to be used
int quantity = 1; // Field for how many items to produce in cell

public CellProd(Cell box, int request)


{
cell = box; // Pass in what cell object to be used
quantity = request; // Pass in how many items to produce in cell
}
public void ThreadRun( )
{
for(int looper=1; looper<=quantity; looper++)
cell.WriteToCell(looper); // "producing"
}
}

public class CellCons


{
Cell cell; // Field to hold cell object to be used
int quantity = 1; // Field for how many items to consume from cell

public CellCons(Cell box, int request)


{
cell = box; // Pass in what cell object to be used
quantity = request; // Pass in how many items to consume from cell
}
public void ThreadRun( )
{
int valReturned;
for(int looper=1; looper<=quantity; looper++)

130
// Consume the result by placing it in valReturned.
valReturned=cell.ReadFromCell( );
}
}

public class Cell


{
int cellContents; // Cell contents
bool readerFlag = false; // State flag
public int ReadFromCell( )
{
lock(this) // Enter synchronization block
{
if (!readerFlag)
{ // Wait until Cell.WriteToCell is done producing
try
{
// Waits for the Monitor.Pulse in WriteToCell
Monitor.Wait(this);
}
catch (SynchronizationLockException e)
{
Console.WriteLine(e);
}
catch (ThreadInterruptedException e)
{
Console.WriteLine(e);
}
}
Console.WriteLine("Consume: {0}",cellContents);
readerFlag = false; // Reset the state flag to say consuming
// is done.
Monitor.Pulse(this); // Pulse tells Cell.WriteToCell that
// Cell.ReadFromCell is done.
} // Exit synchronization block
return cellContents;
}

public void WriteToCell(int n)


{

131
lock(this) // Enter synchronization block
{
if (readerFlag)
{ // Wait until Cell.ReadFromCell is done consuming.
try
{
Monitor.Wait(this); // Wait for the Monitor.Pulse in
// ReadFromCell
}
catch (SynchronizationLockException e)
{
Console.WriteLine(e);
}
catch (ThreadInterruptedException e)
{
Console.WriteLine(e);
}
}
cellContents = n;
Console.WriteLine("Produce: {0}",cellContents);
readerFlag = true; // Reset the state flag to say producing
// is done
Monitor.Pulse(this); // Pulse tells Cell.ReadFromCell that
// Cell.WriteToCell is done.
} // Exit synchronization block
}
}

Example Output

Produce: 1
Consume: 1
Produce: 2
Consume: 2
Produce: 3
Consume: 3
...
...
Produce: 20
Consume: 20

132
Example 3: Using a thread pool

The following example shows how to use a thread pool. It first creates a ManualResetEvent
object, which enables the program to know when the thread pool has finished running all of the
work items. Next, it attempts to add one thread to the thread pool. If this succeeds, it adds the rest
(four in this example). The thread pool will then put work items into available threads. The
WaitOne method on eventX is called, which causes the rest of the program to wait until the event
is triggered (with the eventX.Set method). Finally, the program prints out the load (the thread that
actually executes a particular work item) on the threads.

// SimplePool.cs
// Simple thread pool example
using System;
using System.Collections;
using System.Threading;

// Useful way to store info that can be passed as a state on a work item
public class SomeState
{
public int Cookie;
public SomeState(int iCookie)
{
Cookie = iCookie;
}
}

public class Alpha


{
public Hashtable HashCount;
public ManualResetEvent eventX;
public static int iCount = 0;
public static int iMaxCount = 0;
public Alpha(int MaxCount)
{
HashCount = new Hashtable(MaxCount);
iMaxCount = MaxCount;
}

// Beta is the method that will be called when the work item is
// serviced on the thread pool.
// That means this method will be called when the thread pool has

133
// an available thread for the work item.
public void Beta(Object state)
{
// Write out the hashcode and cookie for the current thread
Console.WriteLine(" {0} {1} :", Thread.CurrentThread.GetHashCode(),
((SomeState)state).Cookie);
// The lock keyword allows thread-safe modification
// of variables accessible across multiple threads.
Console.WriteLine(
"HashCount.Count=={0}, Thread.CurrentThread.GetHashCode()=={1}",
HashCount.Count,
Thread.CurrentThread.GetHashCode());
lock (HashCount)
{
if (!HashCount.ContainsKey(Thread.CurrentThread.GetHashCode()))
HashCount.Add (Thread.CurrentThread.GetHashCode(), 0);
HashCount[Thread.CurrentThread.GetHashCode()] =
((int)HashCount[Thread.CurrentThread.GetHashCode()])+1;
}

// Do some busy work.


// Note: Depending on the speed of your machine, if you
// increase this number, the dispersement of the thread
// loads should be wider.
int iX = 2000;
Thread.Sleep(iX);
// The Interlocked.Increment method allows thread-safe modification
// of variables accessible across multiple threads.
Interlocked.Increment(ref iCount);
if (iCount == iMaxCount)
{
Console.WriteLine();
Console.WriteLine("Setting eventX ");
eventX.Set();
}
}
}

public class SimplePool


{

134
public static int Main(string[] args)
{
Console.WriteLine("Thread Pool Sample:");
bool W2K = false;
int MaxCount = 10; // Allow a total of 10 threads in the pool
// Mark the event as unsignaled.
ManualResetEvent eventX = new ManualResetEvent(false);
Console.WriteLine("Queuing {0} items to Thread Pool", MaxCount);
Alpha oAlpha = new Alpha(MaxCount); // Create the work items.
// Make sure the work items have a reference to the signaling event.
oAlpha.eventX = eventX;
Console.WriteLine("Queue to Thread Pool 0");
try
{
// Queue the work items, which has the added effect of checking
// which OS is running.
ThreadPool.QueueUserWorkItem(new WaitCallback(oAlpha.Beta),
new SomeState(0));
W2K = true;
}
catch (NotSupportedException)
{
Console.WriteLine("These API's may fail when called on a non-Windows 2000 system.");
W2K = false;
}
if (W2K) // If running on an OS which supports the ThreadPool methods.
{
for (int iItem=1;iItem < MaxCount;iItem++)
{
// Queue the work items:
Console.WriteLine("Queue to Thread Pool {0}", iItem);
ThreadPool.QueueUserWorkItem(new WaitCallback(oAlpha.Beta),new
SomeState(iItem));
}
Console.WriteLine("Waiting for Thread Pool to drain");
// The call to exventX.WaitOne sets the event to wait until
// eventX.Set() occurs.
// (See oAlpha.Beta).
// Wait until event is fired, meaning eventX.Set() was called:
eventX.WaitOne(Timeout.Infinite,true);

135
// The WaitOne won't return until the event has been signaled.
Console.WriteLine("Thread Pool has been drained (Event fired)");
Console.WriteLine();
Console.WriteLine("Load across threads");
foreach(object o in oAlpha.HashCount.Keys)
Console.WriteLine("{0} {1}", o, oAlpha.HashCount[o]);
}
return 0;
}
}

Example Output

Note The following output will vary from one computer to another.
Thread Pool Sample:
Queuing 10 items to Thread Pool
Queue to Thread Pool 0
Queue to Thread Pool 1
...
...
Queue to Thread Pool 9
Waiting for Thread Pool to drain
98 0 :
HashCount.Count==0, Thread.CurrentThread.GetHashCode()==98
100 1 :
HashCount.Count==1, Thread.CurrentThread.GetHashCode()==100
98 2 :
...
...
Setting eventX
Thread Pool has been drained (Event fired)

Load across threads


101 2
100 3
98 4
102 1

Example 4: Using the Mutex object

136
You can use a mutex object to protect a shared resource from simultaneous access by multiple
threads or processes. The state of a mutex object is either set to signaled, when it is not owned
by any thread, or nonsignaled, when it is owned. Only one thread at a time can own a mutex
object. For example, to prevent two threads from writing to shared memory at the same time,
each thread waits for ownership of a mutex object before executing the code that accesses the
memory. After writing to the shared memory, the thread releases the mutex object.

This example demonstrates how to use the classes Mutex, AutoResetEvent, and WaitHandle in
processing threads. It also demonstrates the methods used in processing the mutex object.

// Mutex.cs
// Mutex object example
using System;
using System.Threading;

public class MutexSample


{
static Mutex gM1;
static Mutex gM2;
const int ITERS = 100;
static AutoResetEvent Event1 = new AutoResetEvent(false);
static AutoResetEvent Event2 = new AutoResetEvent(false);
static AutoResetEvent Event3 = new AutoResetEvent(false);
static AutoResetEvent Event4 = new AutoResetEvent(false);

public static void Main(String[] args)


{
Console.WriteLine("Mutex Sample ...");
// Create Mutex initialOwned, with name of "MyMutex".
gM1 = new Mutex(true,"MyMutex");
// Create Mutex initialOwned, with no name.
gM2 = new Mutex(true);
Console.WriteLine(" - Main Owns gM1 and gM2");

AutoResetEvent[] evs = new AutoResetEvent[4];


evs[0] = Event1; // Event for t1
evs[1] = Event2; // Event for t2
evs[2] = Event3; // Event for t3
evs[3] = Event4; // Event for t4

MutexSample tm = new MutexSample( );

137
Thread t1 = new Thread(new ThreadStart(tm.t1Start));
Thread t2 = new Thread(new ThreadStart(tm.t2Start));
Thread t3 = new Thread(new ThreadStart(tm.t3Start));
Thread t4 = new Thread(new ThreadStart(tm.t4Start));
t1.Start( ); // Does Mutex.WaitAll(Mutex[] of gM1 and gM2)
t2.Start( ); // Does Mutex.WaitOne(Mutex gM1)
t3.Start( ); // Does Mutex.WaitAny(Mutex[] of gM1 and gM2)
t4.Start( ); // Does Mutex.WaitOne(Mutex gM2)

Thread.Sleep(2000);
Console.WriteLine(" - Main releases gM1");
gM1.ReleaseMutex( ); // t2 and t3 will end and signal

Thread.Sleep(1000);
Console.WriteLine(" - Main releases gM2");
gM2.ReleaseMutex( ); // t1 and t4 will end and signal

// Waiting until all four threads signal that they are done.
WaitHandle.WaitAll(evs);
Console.WriteLine("... Mutex Sample");
}

public void t1Start( )


{
Console.WriteLine("t1Start started, Mutex.WaitAll(Mutex[])");
Mutex[] gMs = new Mutex[2];
gMs[0] = gM1; // Create and load an array of Mutex for WaitAll call
gMs[1] = gM2;
Mutex.WaitAll(gMs); // Waits until both gM1 and gM2 are released
Thread.Sleep(2000);
Console.WriteLine("t1Start finished, Mutex.WaitAll(Mutex[]) satisfied");
Event1.Set( ); // AutoResetEvent.Set() flagging method is done
}

public void t2Start( )


{
Console.WriteLine("t2Start started, gM1.WaitOne( )");
gM1.WaitOne( ); // Waits until Mutex gM1 is released
Console.WriteLine("t2Start finished, gM1.WaitOne( ) satisfied");
Event2.Set( ); // AutoResetEvent.Set() flagging method is done

138
}

public void t3Start( )


{
Console.WriteLine("t3Start started, Mutex.WaitAny(Mutex[])");
Mutex[] gMs = new Mutex[2];
gMs[0] = gM1; // Create and load an array of Mutex for WaitAny call
gMs[1] = gM2;
Mutex.WaitAny(gMs); // Waits until either Mutex is released
Console.WriteLine("t3Start finished, Mutex.WaitAny(Mutex[])");
Event3.Set( ); // AutoResetEvent.Set() flagging method is done
}

public void t4Start( )


{
Console.WriteLine("t4Start started, gM2.WaitOne( )");
gM2.WaitOne( ); // Waits until Mutex gM2 is released
Console.WriteLine("t4Start finished, gM2.WaitOne( )");
Event4.Set( ); // AutoResetEvent.Set() flagging method is done
}
}

Sample Output

Mutex Sample ...


- Main Owns gM1 and gM2
t1Start started, Mutex.WaitAll(Mutex[])
t2Start started, gM1.WaitOne( )
t3Start started, Mutex.WaitAny(Mutex[])
t4Start started, gM2.WaitOne( )
- Main releases gM1
t2Start finished, gM1.WaitOne( ) satisfied
t3Start finished, Mutex.WaitAny(Mutex[])
- Main releases gM2
t1Start finished, Mutex.WaitAll(Mutex[]) satisfied
t4Start finished, gM2.WaitOne( )
... Mutex Sample
Note The output of the example may vary from one machine to another and from one run to
another. The speed and operating system of the machine running the sample can affect the
output order. In multithread environments, events may not occur in the order that you expect.

139
Unsafe Code Tutorial

This tutorial demonstrates how to use unsafe code (code using pointers) in C#.

The use of pointers is rarely required in C#, but there are some situations that require them. As
examples, using an unsafe context to allow pointers is warranted by the following cases:

• Dealing with existing structures on disk

• Advanced COM or Platform Invoke scenarios that involve structures with pointers in them

• Performance-critical code

The use of unsafe context in other situations is discouraged. Specifically, an unsafe context
should not be used to attempt to write C code in C#.

Caution Code written using an unsafe context cannot be verified to be safe, so it will be
executed only when the code is fully trusted. In other words, unsafe code cannot be executed in
an untrusted environment. For example, you cannot run unsafe code directly from the Internet.
This tutorial includes the following examples:

• Example 1 Uses pointers to copy an array of bytes.

• Example 2 Shows how to call the Windows ReadFile function.

• Example 3 Shows how to print the Win32 version of the executable file.

Example 1

The following example uses pointers to copy an array of bytes from src to dst. Compile the
example with the /unsafe option.

// fastcopy.cs
// compile with: /unsafe
using System;

class Test
{
// The unsafe keyword allows pointers to be used within
// the following method:
static unsafe void Copy(byte[] src, int srcIndex,
byte[] dst, int dstIndex, int count)
{
if (src == null || srcIndex < 0 ||

140
dst == null || dstIndex < 0 || count < 0)
{
throw new ArgumentException();
}
int srcLen = src.Length;
int dstLen = dst.Length;
if (srcLen - srcIndex < count ||
dstLen - dstIndex < count)
{
throw new ArgumentException();
}

// The following fixed statement pins the location of


// the src and dst objects in memory so that they will
// not be moved by garbage collection.
fixed (byte* pSrc = src, pDst = dst)
{
byte* ps = pSrc;
byte* pd = pDst;

// Loop over the count in blocks of 4 bytes, copying an


// integer (4 bytes) at a time:
for (int n =0 ; n < count/4 ; n++)
{
*((int*)pd) = *((int*)ps);
pd += 4;
ps += 4;
}

// Complete the copy by moving any bytes that weren't


// moved in blocks of 4:
for (int n =0; n < count%4; n++)
{
*pd = *ps;
pd++;
ps++;
}
}
}

141
static void Main(string[] args)
{
byte[] a = new byte[100];
byte[] b = new byte[100];
for(int i=0; i<100; ++i)
a[i] = (byte)i;
Copy(a, 0, b, 0, 100);
Console.WriteLine("The first 10 elements are:");
for(int i=0; i<10; ++i)
Console.Write(b[i] + " ");
Console.WriteLine("\n");
}
}

Example Output

The first 10 elements are:


0123456789

Code Discussion

• Notice the use of the unsafe keyword, which allows pointers to be used within the Copy
method.
• The fixed statement is used to declare pointers to the source and destination arrays. It
pins the location of the src and dst objects in memory so that they will not be moved by
garbage collection. The objects will be unpinned when the fixed block completes
• Unsafe code increases the performance by getting rid of array bounds checks.

Example 2

This example shows how to call the Windows ReadFile function from the Platform SDK, which
requires the use of an unsafe context because the read buffer requires a pointer as a parameter.

// readfile.cs
// compile with: /unsafe
// arguments: readfile.cs

// Use the program to read and display a text file.


using System;

142
using System.Runtime.InteropServices;
using System.Text;

class FileReader
{
const uint GENERIC_READ = 0x80000000;
const uint OPEN_EXISTING = 3;
IntPtr handle;

[DllImport("kernel32", SetLastError=true)]
static extern unsafe IntPtr CreateFile(
string FileName, // file name
uint DesiredAccess, // access mode
uint ShareMode, // share mode
uint SecurityAttributes, // Security Attributes
uint CreationDisposition, // how to create
uint FlagsAndAttributes, // file attributes
int hTemplateFile // handle to template file
);

[DllImport("kernel32", SetLastError=true)]
static extern unsafe bool ReadFile(
IntPtr hFile, // handle to file
void* pBuffer, // data buffer
int NumberOfBytesToRead, // number of bytes to read
int* pNumberOfBytesRead, // number of bytes read
int Overlapped // overlapped buffer
);

[DllImport("kernel32", SetLastError=true)]
static extern unsafe bool CloseHandle(
IntPtr hObject // handle to object
);

public bool Open(string FileName)


{
// open the existing file for reading
handle = CreateFile(
FileName,
GENERIC_READ,

143
0,
0,
OPEN_EXISTING,
0,
0);

if (handle != IntPtr.Zero)
return true;
else
return false;
}

public unsafe int Read(byte[] buffer, int index, int count)


{
int n = 0;
fixed (byte* p = buffer)
{
if (!ReadFile(handle, p + index, count, &n, 0))
return 0;
}
return n;
}

public bool Close()


{
// close file handle
return CloseHandle(handle);
}
}

class Test
{
public static int Main(string[] args)
{
if (args.Length != 1)
{
Console.WriteLine("Usage : ReadFile <FileName>");
return 1;
}

144
if (! System.IO.File.Exists(args[0]))
{
Console.WriteLine("File " + args[0] + " not found.");
return 1;
}

byte[] buffer = new byte[128];


FileReader fr = new FileReader();

if (fr.Open(args[0]))
{

// Assume that an ASCII file is being read


ASCIIEncoding Encoding = new ASCIIEncoding();

int bytesRead;
do
{
bytesRead = fr.Read(buffer, 0, buffer.Length);
string content = Encoding.GetString(buffer,0,bytesRead);
Console.Write("{0}", content);
}
while ( bytesRead > 0);

fr.Close();
return 0;
}
else
{
Console.WriteLine("Failed to open requested file");
return 1;
}
}
}

Example Input

The following input from readfile.txt produces the output shown in Example Output when you
compile and run this sample.

line 1

145
line 2

Example Output

line 1
line 2

Code Discussion

The byte array passed into the Read function is a managed type. This means that the common
language runtime garbage collector could relocate the memory used by the array at will. The
fixed statement allows you to both get a pointer to the memory used by the byte array and to
mark the instance so that the garbage collector won't move it.

At the end of the fixed block, the instance will be marked so that it can be moved. This capability
is known as declarative pinning. The nice part about pinning is that there is very little overhead
unless a garbage collection occurs in the fixed block, which is an unlikely occurrence.

Example 3

This example reads and displays the Win32 version number of the executable file, which is the
same as the assembly version number in this example. The executable file, in this example, is
printversion.exe. The example uses the Platform SDK functions VerQueryValue,
GetFileVersionInfoSize, and GetFileVersionInfo to retrieve specified version information from the
specified version-information resource.

This example uses pointers because it simplifies the use of methods whose signatures use
pointers to pointers, which are common in the Win32 APIs.

// printversion.cs
// compile with: /unsafe
using System;
using System.Reflection;
using System.Runtime.InteropServices;

// Give this assembly a version number:


[assembly:AssemblyVersion("4.3.2.1")]

public class Win32Imports


{
[DllImport("version.dll")]
public static extern bool GetFileVersionInfo (string sFileName,

146
int handle, int size, byte[] infoBuffer);
[DllImport("version.dll")]
public static extern int GetFileVersionInfoSize (string sFileName,
out int handle);

// The third parameter - "out string pValue" - is automatically


// marshaled from ANSI to Unicode:
[DllImport("version.dll")]
unsafe public static extern bool VerQueryValue (byte[] pBlock,
string pSubBlock, out string pValue, out uint len);
// This VerQueryValue overload is marked with 'unsafe' because
// it uses a short*:
[DllImport("version.dll")]
unsafe public static extern bool VerQueryValue (byte[] pBlock,
string pSubBlock, out short *pValue, out uint len);
}

public class C
{
// Main is marked with 'unsafe' because it uses pointers:
unsafe public static int Main ()
{
try
{
int handle = 0;
// Figure out how much version info there is:
int size =
Win32Imports.GetFileVersionInfoSize("printversion.exe",
out handle);

if (size == 0) return -1;

byte[] buffer = new byte[size];

if (!Win32Imports.GetFileVersionInfo("printversion.exe", handle, size, buffer))


{
Console.WriteLine("Failed to query file version information.");
return 1;
}

147
short *subBlock = null;
uint len = 0;
// Get the locale info from the version info:
if (!Win32Imports.VerQueryValue (buffer, @"\VarFileInfo\Translation", out subBlock,
out len))
{
Console.WriteLine("Failed to query version information.");
return 1;
}

string spv = @"\StringFileInfo\" + subBlock[0].ToString("X4") +


subBlock[1].ToString("X4") + @"\ProductVersion";

byte *pVersion = null;


// Get the ProductVersion value for this program:
string versionInfo;

if (!Win32Imports.VerQueryValue (buffer, spv, out versionInfo, out len))


{
Console.WriteLine("Failed to query version information.");
return 1;
}

Console.WriteLine ("ProductVersion == {0}", versionInfo);


}
catch (Exception e)
{
Console.WriteLine ("Caught unexpected exception " + e.Message);
}

return 0;
}
}

Example Output

ProductVersion == 4.3.2.1

OLE DB Tutorial

148
OLE DB is a COM-based application programming interface (API) for accessing data. OLE DB
supports accessing data stored in any format (databases, spreadsheets, text files, and so on) for
which an OLE DB provider is available. Each OLE DB provider exposes data from a particular
type of data source (for example SQL Server databases, Microsoft Access databases, or
Microsoft Excel spreadsheets).

This tutorial demonstrates using a Microsoft Access database from a C# application.

This tutorial demonstrates how to use a Microsoft Access database from C#. It shows how you
can create a dataset and add tables to it from a database. The BugTypes.mdb database used in
the sample program is a Microsoft Access 2000 .MDB file.

Example

This program accesses the BugTypes.mdb database, creates a dataset, adds the tables to it, and
displays the number of tables, columns, and rows. It also displays the titles of each row.

// OleDbSample.cs
using System;
using System.Data;
using System.Data.OleDb;
using System.Xml.Serialization;

public class MainClass {


public static void Main ()
{
// Set Access connection and select strings.
// The path to BugTypes.MDB must be changed if you build
// the sample from the command line:
#if USINGPROJECTSYSTEM
string strAccessConn = "Provider=Microsoft.Jet.OLEDB.4.0;Data
Source=..\\..\\BugTypes.MDB";
#else
string strAccessConn = "Provider=Microsoft.Jet.OLEDB.4.0;Data
Source=BugTypes.MDB";
#endif
string strAccessSelect = "SELECT * FROM Categories";

// Create the dataset and add the Categories table to it:


DataSet myDataSet = new DataSet();
OleDbConnection myAccessConn = null;
try

149
{
myAccessConn = new OleDbConnection(strAccessConn);
}
catch(Exception ex)
{
Console.WriteLine("Error: Failed to create a database connection. \n{0}",
ex.Message);
return;
}

try
{

OleDbCommand myAccessCommand = new


OleDbCommand(strAccessSelect,myAccessConn);
OleDbDataAdapter myDataAdapter = new OleDbDataAdapter(myAccessCommand);

myAccessConn.Open();
myDataAdapter.Fill(myDataSet,"Categories");

}
catch (Exception ex)
{
Console.WriteLine("Error: Failed to retrieve the required data from the
DataBase.\n{0}", ex.Message);
return;
}
finally
{
myAccessConn.Close();
}

// A dataset can contain multiple tables, so let's get them


// all into an array:
DataTableCollection dta = myDataSet.Tables;
foreach (DataTable dt in dta)
{
Console.WriteLine("Found data table {0}", dt.TableName);
}

150
// The next two lines show two different ways you can get the
// count of tables in a dataset:
Console.WriteLine("{0} tables in data set", myDataSet.Tables.Count);
Console.WriteLine("{0} tables in data set", dta.Count);
// The next several lines show how to get information on
// a specific table by name from the dataset:
Console.WriteLine("{0} rows in Categories table",
myDataSet.Tables["Categories"].Rows.Count);
// The column info is automatically fetched from the database,
// so we can read it here:
Console.WriteLine("{0} columns in Categories table",
myDataSet.Tables["Categories"].Columns.Count);
DataColumnCollection drc = myDataSet.Tables["Categories"].Columns;
int i = 0;
foreach (DataColumn dc in drc)
{
// Print the column subscript, then the column's name
// and its data type:
Console.WriteLine("Column name[{0}] is {1}, of type {2}",i++ , dc.ColumnName,
dc.DataType);
}
DataRowCollection dra = myDataSet.Tables["Categories"].Rows;
foreach (DataRow dr in dra)
{
// Print the CategoryID as a subscript, then the CategoryName:
Console.WriteLine("CategoryName[{0}] is {1}", dr[0], dr[1]);
}

}
}

Output

The Categories table of the BugTypes.mdb database contains the following information.

Category ID Category Name


1 Bugbash Stuff
2 Appweek Bugs
3 .NET Framework Reports
4 Internal Support

151
When you run the sample, the following output will be displayed on the screen:

Found data table Categories


1 tables in data set
1 tables in data set
4 rows in Categories table
2 columns in Categories table
Column name[0] is CategoryID, of type System.Int32
Column name[1] is CategoryName, of type System.String
CategoryName[1] is Bugbash Stuff
CategoryName[2] is Appweek Bugs
CategoryName[3] is .NET Framework Reports
CategoryName[4] is Internal Support

152

Das könnte Ihnen auch gefallen