189
MORE TUTORIALS OVERALL TUTORIAL TOPICS Tutorial Description Level Hello World Shows a Hello World application. Simple Command Line Parameters Shows simple command-line processing; also shows array indexing. Simple Arrays Shows how to use arrays. Simple Properties Shows how properties are declared and used; also demonstrates abstract properties. Simple Libraries Shows how to use compiler options to create a DLL library from multiple source files; also, how to use them in other programs. Simple Versioning Shows the use of override and new to support versioning. Simple Collection Classes Shows how to make collection classes that can be used with foreach. Intermediate 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 properties. Indexed properties allow you to use a class that represents an array-like collection of several different kinds of things. Intermediate User-Defined Conversions Shows how to define conversions to and from user-defined types. Intermediate Operator Overloading Shows how user-defined classes can overload operators. Intermediate Delegates Shows how delegates are declared, mapped, and combined. Intermediate Events Shows how to use events in C#. Intermediate 1

More Tutorial - C#

  • Upload
    bharti

  • View
    124

  • Download
    2

Embed Size (px)

Citation preview

Page 1: More Tutorial - C#

MORE TUTORIALS

OVERALL TUTORIAL TOPICS

Tutorial Description Level

Hello World Shows a Hello World application. Simple

Command Line Parameters

Shows simple command-line processing; also shows array indexing.

Simple

Arrays Shows how to use arrays. Simple

Properties Shows how properties are declared and used; also demonstrates abstract properties.

Simple

Libraries Shows how to use compiler options to create a DLL library from multiple source files; also, how to use them in other programs.

Simple

Versioning Shows the use of override and new to support versioning.

Simple

Collection Classes Shows how to make collection classes that can be used with foreach.

Intermediate

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 properties. Indexed properties allow you to use a class that represents an array-like collection of several different kinds of things.

Intermediate

User-Defined Conversions

Shows how to define conversions to and from user-defined types.

Intermediate

Operator Overloading

Shows how user-defined classes can overload operators.

Intermediate

Delegates Shows how delegates are declared, mapped, and combined.

Intermediate

Events Shows how to use events in C#. Intermediate

Explicit Interface Implementation

Demonstrates how to explicitly implement interface members.

Intermediate

Conditional Methods Demonstrates conditional methods, which provide a powerful mechanism by which calls to methods can be included or omitted depending on whether a symbol is defined.

Intermediate

XML Documentation Shows how to document code by using XML. Intermediate

Platform Invoke Shows how to call platform invokes (exported DLL functions) from C#.

Advanced

1

Page 2: More Tutorial - C#

COM Interop Part 1 Shows how to use C# to interoperate with COM objects. Covers a C# client.

Advanced

COM Interop Part 2 Shows how to use C# to interoperate with COM objects. Covers a C# server.

Advanced

Attributes Shows how to create custom attribute classes, use them in code, and query them through reflection.

Advanced

Security Discusses .NET Framework security and shows two ways to modify security permissions in C#: permission classes and permission attributes.

Advanced

Threading Demonstrates various thread activities such as creating and executing a thread, synchronizing threads, interacting between threads, using a thread pool, and using a mutex object.

Advanced

Unsafe Code Shows how to use pointers. Advanced

OLE DB Shows how to use OLE DB in C# by connecting to a Microsoft Access database.

Advanced

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).

2

Page 3: More Tutorial - C#

The System.Console class contains a WriteLine method that can be used to display a

string to the console.

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++)

{

3

Page 4: More Tutorial - C#

Console.WriteLine("{0}", args[i]);

}

}

}

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.

4

Page 5: More Tutorial - C#

Example 1

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

// 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;

5

Page 6: More Tutorial - C#

public class CommandLine2

{

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

Page 7: More Tutorial - C#

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

Page 8: More Tutorial - C#

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

Page 9: More Tutorial - C#

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

Page 10: More Tutorial - C#

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

Page 11: More Tutorial - C#

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

Page 12: More Tutorial - C#

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

Page 13: More Tutorial - C#

// 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

Page 14: More Tutorial - C#

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

Page 15: More Tutorial - C#

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

Page 16: More Tutorial - C#

}

// 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

Page 17: More Tutorial - C#

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

Page 18: More Tutorial - C#

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

Page 19: More Tutorial - C#

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 DigitCounter.cs

Specifies the files to compile and place in the DLL.

Compiling the Client

19

Page 20: More Tutorial - C#

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

Page 21: More Tutorial - C#

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;

21

Page 22: More Tutorial - C#

// The following using directive makes the types defined in the Functions

// 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

22

Page 23: More Tutorial - C#

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

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()

{

23

Page 24: More Tutorial - C#

return "MyBase-Meth2";

}

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());

}

}

24

Page 25: More Tutorial - C#

Output

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.

25

Page 26: More Tutorial - C#

// tokens.cs

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)

{

26

Page 27: More Tutorial - C#

position++;

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

Page 28: More Tutorial - C#

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

Page 29: More Tutorial - C#

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

Page 30: More Tutorial - C#

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

Page 31: More Tutorial - C#

// 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

Page 32: More Tutorial - C#

{

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

Page 33: More Tutorial - C#

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

Page 34: More Tutorial - C#

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

Page 35: More Tutorial - C#

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

Page 36: More Tutorial - C#

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

Page 37: More Tutorial - C#

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

Page 38: More Tutorial - C#

{

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

Page 39: More Tutorial - C#

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

39

Page 40: More Tutorial - C#

parameter 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

Page 41: More Tutorial - C#

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

Page 42: More Tutorial - C#

{

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

Page 43: More Tutorial - C#

// 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

Page 44: More Tutorial - C#

// 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

Page 45: More Tutorial - C#

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

Page 46: More Tutorial - C#

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

Page 47: More Tutorial - C#

{

// 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

Page 48: More Tutorial - C#

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

Page 49: More Tutorial - C#

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

Page 50: More Tutorial - C#

}

}

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

Page 51: More Tutorial - C#

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

Page 52: More Tutorial - C#

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

Page 53: More Tutorial - C#

// 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

Page 54: More Tutorial - C#

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

Page 55: More Tutorial - C#

// 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

Page 56: More Tutorial - C#

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:

Delegates and Events

56

Page 57: More Tutorial - C#

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;

}

}

// Declare a delegate type for processing a book:

57

Page 58: More Tutorial - C#

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

Page 59: More Tutorial - C#

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));

Console.WriteLine("Average Paperback Book Price: ${0:#.##}",

59

Page 60: More Tutorial - C#

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,

the argument passed to the new expression is special — it is written like a method call,

but without the arguments to the method.

60

Page 61: More Tutorial - C#

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)

{

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

}

61

Page 62: More Tutorial - C#

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!

Invoking delegate b:

Goodbye, B!

Invoking delegate c:

62

Page 63: More Tutorial - 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.

Events Tutorial

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

63

Page 64: More Tutorial - 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

{

using System.Collections;

// A delegate type for hooking up change notifications.

64

Page 65: More Tutorial - C#

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

{

base[index] = value;

OnChanged(EventArgs.Empty);

}

}

65

Page 66: More Tutorial - C#

}

}

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()

{

// Create a new list.

ListWithChangedEvent list = new ListWithChangedEvent();

66

Page 67: More Tutorial - C#

// 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.

if (Changed != null)

Changed(this, e);

Invoking an event can only be done from within the class that declared the event.

67

Page 68: More Tutorial - C#

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.

.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.

68

Page 69: More Tutorial - C#

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:

public override int Add(object value)

{

int i = base.Add(value);

OnChanged(EventArgs.Empty);

return i;

}

69

Page 70: More Tutorial - C#

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:

private void ListChanged(object sender, EventArgs e)

{

Console.WriteLine("This is called when the event fires.");

}

public void Detach()

70

Page 71: More Tutorial - C#

{

// 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 TutorialThis 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

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

71

Page 72: More Tutorial - C#

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":

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

72

Page 73: More Tutorial - C#

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

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

73

Page 74: More Tutorial - C#

{

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;

}

float IMetricDimensions.Width()

{

return widthInches * 2.54f;

}

public static void Main()

74

Page 75: More Tutorial - C#

{

// 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()

{

return widthInches;

}

// Explicit implementation:

float IMetricDimensions.Length()

{

75

Page 76: More Tutorial - C#

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.

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.)

76

Page 77: More Tutorial - C#

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)

{

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

}

}

}

77

Page 78: More Tutorial - C#

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++)

{

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

}

}

Trace.Message("Main Ending");

78

Page 79: More Tutorial - C#

}

}

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 TutorialThis tutorial shows how to document code using XML.

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.

79

Page 80: More Tutorial - C#

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

{

get

{

if ( myName == null )

{

80

Page 81: More Tutorial - C#

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

return 0;

}

}

81

Page 82: More Tutorial - C#

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>

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>

82

Page 83: More Tutorial - C#

</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>

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

83

Page 84: More Tutorial - C#

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.

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.

84

Page 85: More Tutorial - C#

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 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.

85

Page 86: More Tutorial - C#

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();

}

}

Output

When you run this example, the string,

Hello World!

86

Page 87: More Tutorial - C#

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;

LONG lfWidth;

LONG lfEscapement;

LONG lfOrientation;

LONG lfWeight;

87

Page 88: More Tutorial - C#

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;

}

For more information on the syntax of the StructLayout attribute, see StructLayoutAttribute

Class.

88

Page 89: More Tutorial - C#

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

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

// Delete the logical font created.

if (!DeleteObject(handle))

89

Page 90: More Tutorial - C#

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.

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,

90

Page 91: More Tutorial - C#

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

(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

91

Page 92: More Tutorial - C#

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#:

//

// declare FilgraphManager as a COM coclass

//

[ComImport, Guid("E436EBB3-524F-11CE-9F53-0020AF0BA770")]

class FilgraphManager

92

Page 93: More Tutorial - C#

{

}

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.

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

93

Page 94: More Tutorial - C#

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();

[id(0x60020003)]

HRESULT GetState( [in] long msTimeout, [out] long* pfs);

[id(0x60020004)]

94

Page 95: More Tutorial - C#

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);

void AddSourceFilter(

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

[Out, MarshalAs(UnmanagedType.Interface)] out object ppUnk);

95

Page 96: More Tutorial - C#

[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.

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.

96

Page 97: More Tutorial - C#

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

the user presses ENTER.

Input Parameters: the location of the AVI file it is

going to display

Returns: void

**************************************************************/

97

Page 98: More Tutorial - C#

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:

QuartzTypeLib.IMediaControl mc =

(QuartzTypeLib.IMediaControl)graphManager;

// Call some methods on a COM interface

// Pass in file to RenderFile method on COM object.

98

Page 99: More Tutorial - C#

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

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;

99

Page 100: More Tutorial - C#

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();

}

// 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

100

Page 101: More Tutorial - C#

// 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;

}

String filename = args[0];

// Check to see if the file exists

if (!System.IO.File.Exists(filename))

{

Console.WriteLine("File " + filename + " not found.");

101

Page 102: More Tutorial - C#

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()

{

// 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");

102

Page 103: More Tutorial - C#

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:

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

103

Page 104: More Tutorial - C#

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

{

public int PrintHi(string name)

{

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

return 33;

}

}

}

104

Page 105: More Tutorial - C#

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));

if (FAILED(hr))

{

printf("Couldn't create the instance!... 0x%x\n", hr);

}

else

{

if (argc > 1)

{

105

Page 106: More Tutorial - C#

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").

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.

106

Page 107: More Tutorial - C#

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

{

get

{

return topic;

}

set

{

topic = value;

107

Page 108: More Tutorial - C#

}

}

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

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:

108

Page 109: More Tutorial - C#

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

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]);

109

Page 110: More Tutorial - C#

}

}

}

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.

// 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;

}

110

Page 111: More Tutorial - C#

// 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()

{

string value = "Author : " + Name;

if (version != 0)

{

value += " Version : " + Version.ToString();

}

return value;

}

private string name;

private int version;

}

111

Page 112: More Tutorial - C#

// 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)

{

foreach (object attribute in member.GetCustomAttributes(true))

{

if (attribute is IsTestedAttribute)

{

return true;

}

}

return false;

}

private static void DumpAttributes(MemberInfo member)

112

Page 113: More Tutorial - C#

{

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

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);

113

Page 114: More Tutorial - C#

}

}

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.

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.

114

Page 115: More Tutorial - C#

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.

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

115

Page 116: More Tutorial - C#

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()

{

// 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

116

Page 117: More Tutorial - C#

// 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

// 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.");

}

117

Page 118: More Tutorial - C#

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.

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;

118

Page 119: More Tutorial - C#

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

{

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.");

}

}

119

Page 120: More Tutorial - C#

// 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()

{

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

120

Page 121: More Tutorial - C#

// 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

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.

121

Page 122: More Tutorial - C#

// 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

// 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

122

Page 123: More Tutorial - C#

{

// 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

// 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.");

123

Page 124: More Tutorial - C#

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

// 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

124

Page 125: More Tutorial - C#

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

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

125

Page 126: More Tutorial - C#

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.");

}

}

};

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.

126

Page 127: More Tutorial - C#

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");

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

127

Page 128: More Tutorial - C#

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:

// 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,

128

Page 129: More Tutorial - C#

// 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

// 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

129

Page 130: More Tutorial - C#

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++)

// 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

{

130

Page 131: More Tutorial - C#

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)

{

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);

}

131

Page 132: More Tutorial - C#

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

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;

132

Page 133: More Tutorial - C#

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

// 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());

133

Page 134: More Tutorial - C#

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

{

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");

134

Page 135: More Tutorial - C#

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);

// 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

135

Page 136: More Tutorial - C#

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

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;

136

Page 137: More Tutorial - C#

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( );

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

137

Page 138: More Tutorial - C#

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

}

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

}

138

Page 139: More Tutorial - C#

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.

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#.

139

Page 140: More Tutorial - 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 ||

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

140

Page 141: More Tutorial - C#

// 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++;

}

}

}

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");

}

}

141

Page 142: More Tutorial - C#

Example Output

The first 10 elements are:

0 1 2 3 4 5 6 7 8 9

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;

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

142

Page 143: More Tutorial - C#

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,

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)

143

Page 144: More Tutorial - C#

{

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;

}

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;

144

Page 145: More Tutorial - C#

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

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.

145

Page 146: More Tutorial - C#

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,

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);

}

146

Page 147: More Tutorial - C#

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;

}

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;

147

Page 148: More Tutorial - C#

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

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;

148

Page 149: More Tutorial - C#

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

{

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);

149

Page 150: More Tutorial - C#

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);

}

// 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

150

Page 151: More Tutorial - C#

// 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

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

151