Skip to content

3. The Basics of the C# Language

3.1. Introduction

We will first treat C# as a classic programming language. We will cover classes later. In a program, there are two things:

  • data
  • the instructions that manipulate them

We generally strive to separate data from instructions:

3.2. C# Data

C# uses the following data types:

  1. integers
  1. floating-point numbers
  2. decimal numbers
  3. characters and strings
  4. Booleans
  5. objects

3.2.1. Predefined data types

C# type
.NET type
Data represented
Suffix
of literal values
Encoding
Value range
char
Char (S)
character
 
2 bytes
Unicode character (UTF-16)
string
String (C)
character string
  
reference to a sequence of Unicode characters
int
Int32 (S)
integer
 
4 bytes
[-2³¹ , 2³¹-1] [–2147483648, 2147483647]
uint
UInt32 (S)
..
U
4 bytes
[0, 2³²-1] [0, 4294967295]
long
Int64 (S)
..
L
8 bytes
[-263, 263 -1] [–9223372036854775808, 9223372036854775807]
ulong
UInt64 (S)
..
UL
8 bytes
[0, 264 -1] [0, 18446744073709551615]
sbyte
 
..
 
1 byte
[-27, 27] [-128, +127]
byte
Byte(s)
..
 
1 byte
[0, 28 -1] [0, 255]
short
Int16 (S)
..
 
2 bytes
[-215, 215-1] [-32768, 32767]
ushort
UInt16 (S)
..
 
2 bytes
[0, 216-1] [0,65535]
float
Single (S)
real number
F
4 bytes
[1.5 × 10⁻⁴⁵, 3.4 × 10³⁸] in absolute value
double
Double (S)
..
D
8 bytes
[-1.7 × 10³⁰⁸, 1.7 × 10³⁰⁸] in absolute value
decimal
Decimal (S)
decimal number
M
16 bytes
[1.0 10⁻²⁸, 7.9 10²⁸] in absolute value with 28 significant digits
bool
Boolean (S)
..
 
1 byte
true, false
object
Object (C)
object reference
  
object reference

Above, we have listed C# types alongside their equivalent .NET types, with the comment (S) if the type is a struct and (C) if the type is a class. We see that there are two possible types for a 32-bit integer: int and Int32. The int type is a C# type. Int32 is a structure belonging to the System namespace. Its full name is therefore System.Int32. The int type is a C# alias that refers to the .NET System.Int32 structure. Similarly, the C# string type is an alias for the .NET System.String type. System.String is a class, not a structure. The two concepts are similar, but with the following fundamental difference:

  • a variable of type Structure is manipulated via its value
  • a variable of type Class is manipulated via its address (reference in object-oriented language).

Both a structure and a class are complex types with attributes and methods. Thus, we can write:

string typeName = 3.GetType().FullName;

In the code above, the literal 3 is by default of type C# int, and thus of type .NET System.Int32. This structure has a GetType() method that returns an object encapsulating the characteristics of the System.Int32 data type. Among these, the FullName property returns the full name of the type. We can therefore see that the literal 3 is a more complex object than it appears at first glance.

Here is a program illustrating these various points:


using System;

namespace Chap1 {
    class P00 {
        static void Main(string[] args) {
            // example 1
            int ent = 2;
            float fl = 10.5F;
            double d = -4.6;
            string s = "test";
            uint ui = 5;
            long l = 1000;
            ulong ul = 1001;
            byte byte = 5;
            short sh = -4;
            ushort ush = 10;
            decimal dec = 10.67M;
            bool b = true;
            Console.WriteLine("Type of ent[{1}]: [{0},{2}]", ent.GetType().FullName, ent, sizeof(int));
            Console.WriteLine("Type of fl[{1}]: [{0},{2}]", fl.GetType().FullName, fl, sizeof(float));
            Console.WriteLine("Type of d[{1}]: [{0},{2}]", d.GetType().FullName, d, sizeof(double));
            Console.WriteLine("Type of s[{1}]: [{0}]", s.GetType().FullName, s);
            Console.WriteLine("Type of ui[{1}]: [{0},{2}]", ui.GetType().FullName, ui, sizeof(uint));
            Console.WriteLine("Type of l[{1}]: [{0},{2}]", l.GetType().FullName, l, sizeof(long));
            Console.WriteLine("Type of ul[{1}]: [{0},{2}]", ul.GetType().FullName, ul, sizeof(ulong));
            Console.WriteLine("Type of b[{1}] : [{0},{2}]", byte.GetType().FullName, byte, sizeof(byte));
            Console.WriteLine("Type of sh[{1}]: [{0},{2}]", sh.GetType().FullName, sh, sizeof(short));
            Console.WriteLine("Type of ush[{1}]: [{0},{2}]", ush.GetType().FullName, ush, sizeof(ushort));
            Console.WriteLine("Type of dec[{1}]: [{0},{2}]", dec.GetType().FullName, dec, sizeof(decimal));
            Console.WriteLine("Type of b[{1}]: [{0},{2}]", b.GetType().FullName, b, sizeof(bool));
        }
    }
}
  • line 7: declaration of an integer ent
  • line 19: the type of a variable v can be obtained via v.GetType().FullName. The size of a structure S can be obtained via sizeof(S). The statement Console.WriteLine("... {0} ... {1} ...", param0, param1, ...) prints " " on the screen, replacing each {i} with the value of the parami expression.
  • Line 22: Since the string type refers to a class and not a structure, the sizeof operator cannot be used.

Here is the result of the execution:

Type of ent[2]: [System.Int32,4]
Type of fl[10,5]: [System.Single,4]
Type of d[-4,6]: [System.Double,8]
Type of s[test]: [System.String]
Type of ui[5]: [System.UInt32,4]
Type of l[1000]: [System.Int64,8]
Type of ul[1001]: [System.UInt64,8]
Type of b[5]: [System.Byte,1]
Type of sh[-4]: [System.Int16,2]
Type of ush[10]: [System.UInt16,2]
Type of dec[10,67]: [System.Decimal,16]
Type of b[True]: [System.Boolean,1]

The display shows .NET types, not C# aliases.

3.2.2. Literal data notation

integer int (32 bits)
145, -7, 0xFF (hexadecimal)
long integer (64 bits) - suffix L
100000L
double
134.789, -45E-18 (-45 × 10⁻¹⁸)
float (suffix F)
134.789F, -45E-18F (-45 × 10⁻¹⁸)
real decimal (suffix M)
100000M
character char
'A', 'b'
string
"today" "c:\\chap1\\paragraph3" @"c:\chap1\paragraph3"
boolean bool
true, false
date
new DateTime(1954,10,13) (year, month, day) for 10/13/1954

Note the two string literals: "c:\\chap1\\paragraph3" and @"c:\chap1\paragraph3". In string literals, the \ character is interpreted. Thus, "\n" represents the end-of-line character and not the sequence of the two characters \ and n. If you wanted that sequence, you would have to write "\\n", where the sequence \\ is interpreted as a single character \. You could also write @"\n" to get the same result. The syntax @"text" requires that text be taken exactly as it is written. This is sometimes called a verbatim string.

3.2.3. Data Declaration

3.2.3.1. Role of declarations

A program manipulates data characterized by a name and a type. This data is stored in memory. When the program is compiled, the compiler assigns each piece of data a memory location characterized by an address and a size. It does this using the declarations made by the programmer.

Furthermore, these declarations allow the compiler to detect programming errors. Thus, the operation

x=x*2;

will be declared invalid if x is a string, for example.

3.2.3.2. Declaring Constants

The syntax for declaring a constant is as follows:

    const type name=value;          //defines constant name=value

For example:

const float myPI = 3.141592F;    

Why declare constants?

  1. The program will be easier to read if the constant is given a meaningful name:
    const float VAT_rate = 0.186F;
  1. Modifying the program will be easier if the "constant" needs to be changed. So in the previous case, if the VAT rate changes to 33%, the only modification needed will be to change the statement defining its value:
    const float tax_rate = 0.33F;

If we had used 0.186 explicitly in the program, we would then have to modify numerous statements.

3.2.3.3. Variable Declaration

A variable is identified by a name and refers to a data type. C# distinguishes between uppercase and lowercase letters. Thus, the variables FIN and fin are different.

Variables can be initialized when they are declared. The syntax for declaring one or more variables is:

 Type_identifier variable1[=value1], variable2=[value2],...;

where Type_identifier is a predefined type or a type defined by the programmer. Optionally, a variable can be initialized at the same time as it is declared.

You can also omit the exact type of a variable by using the keyword var instead of Type_identifier:

 var variable1=value1,variable2=value2,...;

The var keyword does not mean that the variables do not have a specific type. The variable variablei has the type of the value valuei assigned to it. Initialization is mandatory here so that the compiler can deduce the variable’s type.

Here is an example:


using System;

namespace Chap1 {
    class P00 {
        static void Main(string[] args) {
            int i = 2;
            Console.WriteLine("Type of int i=2: {0},{1}", i.GetType().Name, i.GetType().FullName);
            var j = 3;
            Console.WriteLine("Type of var j=3: {0},{1}", j.GetType().Name, j.GetType().FullName);
            var today = DateTime.Now;
            Console.WriteLine("Type of var today: {0},{1}", today.GetType().Name, today.GetType().FullName);
        }
    }
}
  • Line 6: an explicitly typed variable
  • line 7: (data).GetType().Name is the short name of (data), (data).GetType().FullName is the full name of (data)
  • line 8: an implicitly typed data type. Because 3 is of type int, j will be of type int.
  • Line 10: Implicitly typed data. Since DateTime.Now is of type DateTime, today will be of type DateTime.

At runtime, we get the following result:

1
2
3
Type of int i=2: Int32, System.Int32
Type of var j=3: Int32, System.Int32
Type of var today: DateTime, System.DateTime

A variable implicitly typed by the var keyword cannot subsequently change its type. Thus, we could not write the following line after line 10 of the code:


            var today = "today";

We will see later that it is possible to declare a type "on the fly" in an expression. This is then an anonymous type, a type to which the user has not given a name. The compiler will give a name to this new type. If data of an anonymous type must be assigned to a variable, the only way to declare it is to use the var keyword.

3.2.4. Conversions between numbers and strings

number -> string
number.ToString()
string -> int
int.Parse(string) or System.Int32.Parse
string -> long
long.Parse(string) or System.Int64.Parse
string -> double
double.Parse(string) or System.Double.Parse(string)
string -> float
float.Parse(string) or System.Float.Parse(string)

Converting a string to a number may fail if the string does not represent a valid number. This results in a fatal error called an exception. This error can be handled by the following try/catch block:

try{
    call to the function likely to generate the exception
} catch (Exception e){
    handle the exception e
}
next statement

If the function does not throw an exception, we proceed to the next statement; otherwise, we enter the body of the catch clause and then proceed to the next statement. We will return to exception handling later. Here is a program demonstrating some techniques for converting between numbers and strings. In this example, the function displays the value of its parameter on the screen. Thus, Display(S) writes the value of S to the screen, where S is of type string.


using System;

namespace Chap1 {
    class P01 {
        static void Main(string[] args) {

            // data
            const int i = 10;
            const long l = 100000;
            const float f = 45.78F;
            double d = -14.98;

            // number --> string
            display(i.ToString());
            print(l.ToString());
            display(f.ToString());
            display(d.ToString());

            //boolean --> string
            const bool b = false;
            display(b.ToString());

            // string --> int
            int i1;
            i1 = int.Parse("10");
            display(i1.ToString());
            try {
                i1 = int.Parse("10.67");
                display(i1.ToString());
            } catch (Exception e) {
                display("Error: " + e.Message);
            }

            // string --> long
            long l1;
            l1 = long.Parse("100");
            display(l1.ToString());
            try {
                l1 = long.Parse("10.675");
                display(l1.ToString());
            } catch (Exception e) {
                display("Error: " + e.Message);
            }

            // string --> double
            double d1;
            d1 = double.Parse("100.87");
            display(d1.ToString());
            try {
                d1 = double.Parse("abcd");
                display(d1.ToString());
            } catch (Exception e) {
                display("Error: " + e.Message);
            }

            // string --> float
            float f1;
            f1 = float.Parse("100.87");
            display(f1.ToString());
            try {
                d1 = float.Parse("abcd");
                display(f1.ToString());
            } catch (Exception e) {
                display("Error: " + e.Message);
            }

        }// end of main

        public static void display(string S) {
            Console.Out.WriteLine("S={0}", S);
        }
    }// end class
}

Lines 30–32 handle any exceptions that may occur. e.Message is the error message associated with the exception e.

The results obtained are as follows:

S=10
S=100000
S=45.78
S=-14.98
S=False
S=10
S=Error: Input string was not in a correct format.
S=100
S=Error: The input string was not in the correct format.
S=100.87
S=Error: Input string was not in a correct format.
S=100.87
S=Error: Input string was not in a correct format.

Note that real numbers in string form must use a comma, not a decimal point. Thus, we write

double d1 = 10.7; 

but

double d2 = int.Parse("10,7");

3.2.5. Data arrays

A C# array is an object that allows data of the same type to be grouped under a single identifier. It is declared as follows:

Type[] array[] = new Type[n]

n is the number of elements the array can contain. The syntax Array[i] refers to the element at index i, where i is in the range [0, n-1]. Any reference to Array[i] where i is not in the range [0, n-1] will throw an exception. An array can be initialized at the same time it is declared:

    int[] integers = new int[] {0, 10, 20, 30};

or more simply:

    int[] integers = {0, 10, 20, 30};

Arrays have a Length property, which is the number of elements in the array.

A two-dimensional array can be declared as follows:

Type[,] array = new Type[n, m];

where n is the number of rows and m is the number of columns. The syntax Array[i,j] refers to element j in row i of the array. A two-dimensional array can also be initialized at the same time it is declared:

    double[,] reals = new double[,] { {0.5, 1.7}, {8.4, -6}};

or more simply:

    double[,] reals={ {0.5, 1.7}, {8.4, -6}};

The number of elements in each dimension can be obtained using the GetLength(i) method, where i=0 represents the dimension corresponding to the first index, i=1 the dimension corresponding to the second index, …

The total number of dimensions is obtained using the Rank property, and the total number of elements using the Length property.

An array of arrays is declared as follows:

Type[][] array = new Type[n][];

The declaration above creates an array of n rows. Each element array[i] is a one-dimensional array reference. These array[i] references are not initialized in the declaration above. Their value is the null reference.

The example below illustrates the creation of an array of arrays:


            // an array of arrays
            string[][] names = new string[3][];
            for (int i = 0; i < names.Length; i++) {
                names[i] = new string[i + 1];
            }//for
            // initialization
            for (int i = 0; i < names.Length; i++) {
                for (int j = 0; j < names[i].Length; j++) {
                    names[i][j] = "name" + i + j;
                }//for j
            }//for i
  • Line 2: An array `names` of 3 elements of type `string[][]`. Each element is an array pointer (an object reference) whose elements are of type `string[]`.
  • lines 3–5: the 3 elements of the names array are initialized. Each now “points” to an array of elements of type string[]. names[i][j] is the jth element of the array of type string[] referenced by names[i].
  • Line 9: Initialization of the element names[i][j] inside a double loop. Here, names[i] is an array of i+1 elements. Since names[i] is an array, names[i].Length is its number of elements.

Here is an example combining the three types of arrays we have just presented:


using System;

namespace Chap1 {
    // arrays

    using System;

    // test class
    public class P02 {
        public static void Main() {
            // a 1-dimensional array initialized
            int[] integers = new int[] { 0, 10, 20, 30 };
            for (int i = 0; i < integers.Length; i++) {
                Console.Out.WriteLine("integers[{0}]={1}", i, integers[i]);
            }//for

            // an initialized 2-dimensional array
            double[,] reals = new double[,] { { 0.5, 1.7 }, { 8.4, -6 } };
            for (int i = 0; i < reals.GetLength(0); i++) {
                for (int j = 0; j < reals.GetLength(1); j++) {
                    Console.Out.WriteLine("reals[{0},{1}]={2}", i, j, reals[i, j]);
                }//for j
            }//for i

            // an array of arrays
            string[][] names = new string[3][];
            for (int i = 0; i < names.Length; i++) {
                names[i] = new string[i + 1];
            }//for
            // initialization
            for (int i = 0; i < names.Length; i++) {
                for (int j = 0; j < names[i].Length; j++) {
                    names[i][j] = "name" + i + j;
                }//for j
            }//for i
            // display
            for (int i = 0; i < names.Length; i++) {
                for (int j = 0; j < names[i].Length; j++) {
                    Console.Out.WriteLine("names[{0}][{1}]={2}", i, j, names[i][j]);
                }//for j
            }//for i
        }//Main
    }//class
}//namespace

When executed, we get the following results:

integers[0]=0
integers[1]=10
integers[2]=20
integers[3]=30
reals[0,0]=0.5
reals[0,1] = 1.7
reals[1,0]=8.4
reals[1,1]=-6
names[0][0]=name00
names[1][0]=name10
names[1][1] = name11
names[2][0]=name20
names[2][1]=name21
names[2][2] = name22

3.3. Basic C# statements

We distinguish between

1 the basic instructions executed by the computer.

2 instructions that control the flow of the program.

Basic instructions become clear when considering the structure of a microcomputer and its peripherals.

  1. Reading information from the keyboard

  2. processing information

  3. Writing information to the screen

3.3.1. Writing to the screen

There are various instructions for writing to the screen:

Console.Out.WriteLine(expression)
Console.WriteLine(expression)
Console.Error.WriteLine(expression)

where expression is any data type that can be converted to a string to be displayed on the screen. All C# or .NET objects have a ToString() method that is used to perform this conversion.

The System.Console class provides access to screen-writing operations (Write, WriteLine). The Console class has two properties, Out and Error, which are TextWriter-type write streams:

  • Console.WriteLine() is equivalent to Console.Out.WriteLine() and writes to the Out stream, which is usually associated with the screen.
  • Console.Error.WriteLine() writes to the Error stream, which is also usually associated with the screen.

The Out and Error streams can be redirected to text files at runtime, as we will see shortly.

3.3.2. Reading data typed on the keyboard

The data stream from the keyboard is represented by the Console.In object of type TextReader. This type of object allows you to read a line of text using the ReadLine method:

    string line = Console.In.ReadLine();

The Console class provides a ReadLine method associated by default with the In stream. We can therefore write:

    string line = Console.ReadLine();

The line typed on the keyboard is stored in the line variable and can then be used by the program. The In stream can be redirected to a file, just like the Out and Error streams.

3.3.3. Input/Output Example

Here is a short program illustrating keyboard/screen input/output operations:


using System;

namespace Chap1 {
    // test class
    public class P03 {
        public static void Main() {

            // Write to the Out stream
            object obj = new object();
            Console.Out.WriteLine(obj);

            // Write to the Error stream
            int i = 10;
            Console.Error.WriteLine("i=" + i);

            // reading a line entered from the keyboard
            Console.Write("Type a line: ");
            string line = Console.ReadLine();
            Console.WriteLine("line={0}", line);
        }//end of main
    }//end class
}
  • Line 9: obj is an object reference
  • line 10: obj is printed to the screen. By default, the obj.ToString() method is called.
  • line 14: you can also write:

            Console.Error.WriteLine("i={0}", i);

The first parameter "i={0}" is the display format; the other parameters are the expressions to be displayed. The {n} elements are "positional" parameters. At runtime, the {n} parameter is replaced by the value of the nth expression.

The result of the execution is as follows:

1
2
3
4
System.Object
i=10
Type a line: I'm here
line=I'm here
  • line 1: the output produced by line 10 of the code. The obj.ToString() method displayed the type name of the obj variable: System.Object. The object type is a C# alias for the .NET System.Object type.

3.3.4. I/O Redirection

In DOS and UNIX, there are three standard devices called:

  1. standard input device - defaults to the keyboard and is numbered 0
  2. standard output device—by default refers to the screen and is numbered 1
  3. standard error device—by default refers to the screen and is numbered 2

In C#, the Console.Out output stream writes to device 1, the Console.Error output stream writes to device 2, and the Console.In input stream reads data from device 0.

When running a program under DOS or Unix, you can specify which devices will be 0, 1, and 2 for the program being executed. Consider the following command line:

pg arg1 arg2 .. argn

After the arguments argi of the pg program, you can redirect the standard I/O devices to files:

0<in.txt
Standard input stream #0 is redirected to the file in.txt. In the program, the Console.In stream will therefore read its data from the file in.txt.
1>out.txt
redirects output #1 to the file out.txt. This means that in the program, the Console.Out stream will write its data to the file out.txt
1>>out.txt
Same as above, but the written data is appended to the current contents of the out.txt file.
2>error.txt
Redirects output #2 to the error.txt file. This means that in the program, the Console.Error stream will write its data to the error.txt file
2>>error.txt
Same as above, but the written data is appended to the current contents of the error.txt file.
1>out.txt 2>error.txt
Devices 1 and 2 are both redirected to files

Note that to redirect the I/O streams of the pg program to files, the pg program does not need to be modified. It is the operating system that determines the nature of devices 0, 1, and 2. Consider the following program:


using System;

namespace Chap1 {

    // redirections
    public class P04 {
        public static void Main(string[] args) {
            // read from In stream
            string data = Console.In.ReadLine();
            // Write to Out stream
            Console.Out.WriteLine("Writing to Out stream: " + data);
            // Write to Error stream
            Console.Error.WriteLine("Writing to Error stream: " + data);
        }//Main
    }//class
}

Let's generate the executable from this source code:

  • in [1]: the executable is created by right-clicking on the project / Build
  • in [2]: in a DOS window, the executable 04.exe has been created in the project's bin/Release folder.

Enter the following commands in the DOS window [2]:

1
2
3
4
5
6
7
8
...\04\bin\Release>echo test >in.txt
...\04\bin\Release>more in.txt
test
...\04\bin\Release>04 0<in.txt 1>out.txt 2>err.txt
...\04\bin\Release>more out.txt
writing to Out stream: test
...\04\bin\Release>more err.txt
writing to Error stream: test
  • Line 1: We put the string "test" into the file in.txt
  • Lines 2-3: Display the contents of the in.txt file for verification
  • Line 4: Execution of the program 04.exe. The In stream is redirected to the file in.txt, the Out stream to the file out.txt, and the Error stream to the file err.txt. The execution does not produce any output.
  • Lines 5–6: contents of the out.txt file. These contents show us that:
  • the in.txt file was read
  • the screen output was redirected to out.txt
  • Lines 7–8: Similar verification for the err.txt file

It is clear that the Out and In streams do not write to the same devices since we were able to redirect them separately.

3.3.5. Assigning the value of an expression to a variable

Here we are interested in the operation variable=expression;

The expression can be of the following types: arithmetic, relational, Boolean, or string

3.3.5.1. Interpretation of the assignment operation

The operation variable=expression;

is itself an expression whose evaluation proceeds as follows:

  • The right-hand side of the assignment is evaluated: the result is a value V.
  • the value V is assigned to the variable
  • the value V is also the value of the assignment, now viewed as an expression.

This is how the operation

    V1=V2=expression

is valid. Due to precedence, the = operator on the far right is evaluated. We therefore have

    V1=(V2=expression)

The expression V2=expression is evaluated and has the value V. The evaluation of this expression caused V to be assigned to V2. The next = operator is then evaluated as:

    V1=V

The value of this expression is still V. Its evaluation causes V to be assigned to V1.

Thus, the operation V1=V2=expression

is an expression whose evaluation

  • causes the value of expression to be assigned to the variables V1 and V2
  • returns the value of expression.

We can generalize this to an expression of the form:

V1=V2=....=Vn=expression

3.3.5.2. Arithmetic expression

The operators for arithmetic expressions are as follows:

  • addition

  • subtraction

* multiplication

/ division: the result is the exact quotient if at least one of the operands is real. If both operands are integers, the result is the integer quotient. Thus, 5/2 -> 2 and 5.0/2 -> 2.5.

% division: the result is the remainder regardless of the nature of the operands, with the quotient being an integer. This is therefore the modulo operation.

There are various mathematical functions. Here are a few:

double Sqrt(double x)
square root
double Cos(double x)
Cosine
double Sin(double x)
sine
double Tan(double x)
Tangent
double Pow(double x, double y)
x to the power of y (x > 0)
double Exp(double x)
Exponential
double Log(double x)
Natural logarithm
double Abs(double x)
absolute value

etc...

All of these functions are defined in a C# class called Math. When using them, you must prefix them with the name of the class in which they are defined. So you would write:

double x, y = 4;
x = Math.Sqrt(y);

The complete definition of the Math class is as follows:

Image

Image

Image

Image

Image

3.3.5.3. Operators in Arithmetic Expressions

The operator precedence when evaluating an arithmetic expression is as follows (from highest to lowest):

[functions], [ ( )], [ *, /, %], [+, -]

Operators within the same block [ ] have the same precedence.

3.3.5.4. Relational expressions

The operators are as follows:

       <, <=, ==, !=, >, >=

Operator precedence

>, >=, <, <=
==, !=

The result of a relational expression is the Boolean value false if the expression is false, true otherwise.

      bool end;
      int x = ...;
      end = x > 4;

Comparison of two characters

Let there be two characters C1 and C2. They can be compared using the operators

    <, <=, ==, !=, >, >=

It is their Unicode codes—which are numbers—that are compared. According to the Unicode order, the following relationships hold:

space < .. < '0' < '1' < .. < '9' < .. < 'A' < 'B' < .. < 'Z' < .. < 'a' < 'b' < .. < 'z'

Comparison of two character strings

They are compared character by character. The first inequality encountered between two characters results in an inequality of the same direction for the strings.

Examples:

Consider comparing the strings "Cat" and "Dog"

This last inequality allows us to conclude that "Cat" < "Dog".

Consider comparing the strings "Cat" and "Kitten". They are equal throughout until the string "Cat" is exhausted. In this case, the exhausted string is declared the "smaller" one. We therefore have the relation

    "Cat" < "Kitten".

Functions for comparing two strings

You can use the relational operators == and != to test whether two strings are equal or not, or the Equals method of the System.String class. For the relations < <= > >=, you must use the CompareTo method of the System. :


using System;

namespace Chap1 {
    class P05 {
        static void Main(string[] args) {
            string string1 = "cat", string2 = "dog";
            int n = string1.CompareTo(string2);
            bool equals = string1.Equals(string2);
            Console.WriteLine("i={0}, equal={1}", n, equal);
            Console.WriteLine("dog==string1:{0}, dog!=string2:{1}", "dog"==string1, "dog" != string2);
        }
    }
}

On line 7, the variable i will have the value:

    0    if the two strings are equal

    1    if string #1 &gt; string #2

    -1    if string #1 &lt; string #2

Line 8: The variable egal will be set to true if the two strings are equal, and false otherwise. Line 10: We use the operators == and != to check whether two strings are equal or not.

The results of the execution:

i=-1, egal=False
string1==string2:False, string1!=string2:False

3.3.5.5. Boolean expressions

The operators that can be used are AND (&&), OR (||), and NOT (!). The result of a Boolean expression is a Boolean.

Operator precedence:

  1. !
  2. &&
  3. ||

            double x = 3.5;
            bool valid = x > 2 && x < 4;

Relational operators have precedence over the && and || operators.

3.3.5.6. Bitwise operations

The operators

Let i and j be two integers.

i<<n
shifts i n bits to the left. The incoming bits are zeros.
i>>n
shifts i n bits to the right. If i is a signed integer (signed char, int, long), the sign bit is preserved.
i & j
performs a bitwise logical AND of i and j.
i | j
performs a bitwise OR operation on i and j.
~i
complements i to 1
i^j
performs the XOR operation on i and j

Consider the following code:


short i = 100, j = -13;
ushort k = 0xF123;
Console.WriteLine("i=0x{0:x4}, j=0x{1:x4}, k=0x{2:x4}", i, j, k);
Console.WriteLine("i<<4=0x{0:x4}, i>>4=0x{1:x4}, k>>4=0x{2:x4}, i&j=0x{3:x4},i|j=0x{4:x4},~i=0x{5:x4},j<<2=0x{6:x4},j>>2=0x{7:x4}", i << 4, i >> 4, k >> 4, (short)(i & j), (short)(i | j), (short)(~i), (short)(j << 2), (short)(j >> 2));
  • The format {0:x4} displays parameter #0 in hexadecimal (x) format using 4 characters (4).

The results of the execution are as follows:

i=0x0064, j=0xfff3, k=0xf123
i<<4=0x0640, i>>4=0x0006, k>>4=0x0f12, i&j=0x0060, i|j=0xfff7, ~i=0xff9b, j<<2=0xffcc, j>>2=0xfffc

3.3.5.7. Combination of operators

a=a+b can be written as a+=b

a=a-b can be written as a-=b

The same applies to the operators /, %, *, <<, >>, &, |, ^. Thus, a=a/2; can be written as a/=2;

3.3.5.8. Increment and decrement operators

The notation variable++ means variable=variable+1 or variable+=1

The variable notation -- means variable = variable - 1 or variable - = 1.

3.3.5.9. The ternary operator ?

The expression

    cond_expr ? expr1:expr2

is evaluated as follows:

1 The expression expr_cond is evaluated. It is a conditional expression with a value of true or false

2 If it is true, the value of the expression is that of expr1, and expr2 is not evaluated.

3 If it is false, the opposite occurs: the value of the expression is that of expr2, and expr1 is not evaluated.

The operation i=(j>4 ? j+1:j-1); will assign j+1 to the variable i if j>4, and j-1 otherwise. This is the same as writing if(j>4) i=j+1; else i=j-1; but it is more concise.

3.3.5.10. General operator precedence

() []  function
gd
! ~ ++ --
dg
new (type) cast operators
dg
*  /  %
gd
+  -
gd
<<  >>
gd
< <=  > >= instanceof
gd
==    !=
gd
&
gd
^
gd
|
gd
&&
gd
||
gd
?   :
dg
= += -= etc. .
dg

gd indicates that, for operators of equal precedence, left-to-right precedence is observed. This means that when an expression contains operators of the same precedence, the operator furthest to the left in the expression is evaluated first. dg indicates right-to-left precedence.

3.3.5.11. Type Casting

It is possible, within an expression, to temporarily change the representation of a value. This is called type casting. The syntax for changing the type of a value in an expression is as follows:

    (type) value

The value then takes on the specified type. This results in a change in the value’s representation.


using System;

namespace Chap1 {
    class P06 {
        static void Main(string[] args) {
            int i = 3, j = 4;
            float f1 = i / j;
            float f2 = (float)i / j;
            Console.WriteLine("f1={0}, f2={1}", f1, f2);
        }
    }
}
  • Line 7: f1 will have the value 0.0. The division 3/4 is an integer division since both operands are of type int.
  • Line 8: (float)i is the value of i converted to float. Now, we have a division between a float and an int. This is a division between floating-point numbers. The value of j will also be converted to float, and then the division of the two floating-point numbers will be performed. f2 will then have the value 0.75.

Here are the results of the execution:

f1=0, f2=0.75

In the operation (float)i:

  • i is a value encoded exactly over 2 bytes
  • (float)i is the same value approximated as a real number over 4 bytes

There is therefore a transcoding of the value of i. This transcoding occurs only for the duration of a calculation; the variable i always retains its int type.

3.4. Program flow control statements

3.4.1. Stop

The Exit method defined in the Environment class allows you to stop the execution of a program.

syntax        void Exit(int status)
Action        stops the current process and returns the status value to the parent process

Exit causes the current process to terminate and returns control to the calling process. The value of status can be used by the calling process. Under DOS, this status variable is returned in the system variable ERRORLEVEL, whose value can be checked in a batch file. Under Unix, with the Bourne shell, the variable $? holds the value of status.

    Environment.Exit(0);

will terminate the program with a status value of 0.

3.4.2. Simple selection structure

 syntax:  if (condition) {actions_true_condition;} else {actions_false_condition;}

notes:

  • The condition is enclosed in parentheses.
  • Each action is terminated by a semicolon.
  • Braces are not followed by a semicolon.
  • Curly braces are only necessary if there is more than one action.
  • The else clause may be omitted.
  • There is no then clause.

The algorithmic equivalent of this structure is the if-then-else structure:

example


    if (x>0)  { nx=nx+1; sx=sx+x; } else dx=dx-x;

Choice structures can be nested:

if(condition1)
if (condition2)
        {......}
      else         //condition2
         {......}
else         //condition1
     {.......}

The following problem sometimes arises:


using System;

namespace Chap1 {
    class P07 {
        static void Main(string[] args) {
            int n = 5;
            if (n > 1)
                if (n > 6)
                    Console.Out.WriteLine(">6");
                else Console.Out.WriteLine("<=6");
        }
    }
}

In the previous example, which if statement does the else on line 10 refer to? The rule is that an else always refers to the nearest if statement: if(n>6), line 8, in the example. Let’s consider another example:


            if (n2 > 1) {
                if (n2 > 6) Console.Out.WriteLine(">6");
       } else Console.Out.WriteLine("<=1");    

Here we wanted to place an else after the if(n2>1) and no else after the if(n2>6). Because of the previous note, we are required to use curly braces: if(n2>1) {...} else ...

3.4.3. Case Structure

The syntax is as follows:

switch(expression) {
    case v1:     
            actions1;
            break;
    case v2:     
            actions2;
            break;
         . .. .. .. .. ..
    default:     
            actions_otherwise;
            break;
}

notes

  • The value of the switch's control expression can be an integer, a character, or a string
  • The control expression is enclosed in parentheses.
  • The default clause may be omitted.
  • The values vi are possible values of the expression. If the expression evaluates to vi, the actions following the case vi clause are executed.
  • The break statement exits the case structure.
  • Each block of statements associated with a value vi must end with a branching statement (break, goto, return, ...); otherwise, the compiler reports an error.

Example

In algorithms

                depending on the selected value
                    case 0
                         end of module
                    case 1
                         execute module M1
                    case 2
                        execute module M2
                    else
                         error<--true
                endcase

In C#

int choice = 2; 
bool error = false;
switch (choice) {
   case 0: return;
   case 1: M1(); break;
   case 2: M2(); break;
   default: error = true; break;
}
}// end of Main

static void M1() {
    Console.WriteLine("M1");
}

static void M2() {
    Console.WriteLine("M2");
}
}

3.4.4. Loop Structures

3.4.4.1. Known number of iterations

For loop

The syntax is as follows:


    for (i=id; i <= if; i = i + ip) { 
       actions; 
      } 

Notes

  • The three arguments of the for loop are enclosed in parentheses and separated by semicolons.
  • Each action in the for loop is terminated by a semicolon.
  • The curly brace is only necessary if there is more than one action.
  • The curly brace is not followed by a semicolon.

The algorithmic equivalent is the for structure:

for i ranging from id to if with a step of ip
    actions
endfor

which can be translated into a while structure:

    i  id
    as long as i <= if
        actions
        i  i + ip
    fintantque

foreach structure

The syntax is as follows:


foreach (Variable type in collection)
    instructions; 
}

Notes

  • collection is a collection of enumerable objects. The collection of enumerable objects we are already familiar with is the array
  • Type is the type of the objects in the collection. For an array, this would be the type of the array elements
  • variable is a variable local to the loop that will successively take on all the values in the collection.

Thus, the following code:


            string[] friends = { "paul", "helene", "jacques", "sylvie" };
            foreach (string name in friends) {
                Console.WriteLine(name);
         }

would display:

paul
hélène
Jacques
sylvie

3.4.4.2. Number of repetitions unknown

There are many structures in C# for this case.

While loop


    while(condition){
          actions;
        } 

The loop continues as long as the condition is true. The loop may never be executed.

Notes:

  • The condition is enclosed in parentheses.
  • Each action is terminated by a semicolon.
  • Curly braces are only necessary if there is more than one action.
  • The curly brace is not followed by a semicolon.

The corresponding algorithmic structure is the while structure:

while condition
    actions
end-while

Do-while structure

The syntax is as follows:


    do{
       statements;
    }while(condition); 

The loop continues until the condition becomes false. Here, the loop is executed at least once.

notes

  • The condition is enclosed in parentheses.
  • Each action is terminated by a semicolon.
  • The curly brace is only necessary if there is more than one action.
  • The curly brace is not followed by a semicolon.

The corresponding algorithmic structure is the "repeat ... until" structure:

repeat
    actions
until condition

Structure for general (for)

The syntax is as follows:


for(start_statements;condition;end_statements){
    statements; 
}

The loop continues as long as the condition is true (evaluated before each iteration). Start_instructions are executed before entering the loop for the first time. End_loop_instructions are executed after each iteration.

Notes

  • The various instructions in start_statements and end_loop_statements are separated by commas.

The corresponding algorithmic structure is as follows:

start_instructions
while condition
    actions
    loop_end_instructions
end_while

Examples

The following code snippets all calculate the sum of the first 10 integers.

            int i, sum, n = 10;
            for (i = 1, sum = 0; i <= n; i = i + 1)
                sum = sum + i;

            for (i = 1, sum = 0; i <= n; sum = sum + i, i = i + 1) ;

            i = 1; sum = 0;
            while (i <= n) { sum += i; i++; }

            i = 1; sum = 0;
            do sum += i++;
            while (i <= n);

3.4.4.3. Loop control statements

break
Exits the for, while, or do...while loop.
continue
moves to the next iteration of for, while, and do ... while loops

3.5. Exception Handling

Many C# functions are capable of throwing exceptions, i.e., errors. When a function is capable of throwing an exception, the programmer should handle it in order to create more error-tolerant programs: you should always avoid an application "crashing" unexpectedly.

Exception handling follows this pattern:

try{
    code that may throw an exception
} catch (Exception e){
    handle exception e
}
next statement

If the function does not throw an exception, the program proceeds to the next statement; otherwise, it enters the body of the catch clause and then proceeds to the next statement. Note the following points:

  • e is an object of type Exception or a derived type. We can be more specific by using types such as IndexOutOfRangeException, FormatException, SystemException, etc.: there are several types of exceptions. By writing catch (Exception e), we indicate that we want to handle all types of exceptions. If the code in the try block is likely to generate several types of exceptions, we may want to be more specific by handling the exception with multiple catch blocks:
try{
    code likely to generate exceptions
} catch (IndexOutOfRangeException e1){
    handle exception e1
}
} catch (FormatException e2){
    handle exception e2
}
next statement
  • You can add a finally clause to try/catch blocks:
try{
    code that may throw an exception
} catch (Exception e){
    handle exception e
}
finally{
    code executed after try or catch
}
next statement

Whether an exception occurs or not, the code in the finally clause will always be executed.

  • In the catch clause, you may not want to use the available Exception object. Instead of writing catch (Exception e){..}, you can write catch(Exception){...} or, more simply, catch {...}.
  • The Exception class has a Message property that contains a message detailing the error that occurred. So if you want to display this message, you would write:
catch (Exception ex) {
    Console.WriteLine("The following error occurred: {0}", ex.Message);
    ...
}//catch
  • The Exception class has a ToString method that returns a string indicating the exception type and the value of the Message property. We can therefore write:
catch (Exception ex){
    Console.WriteLine("The following error occurred: {0}", ex.ToString());
    ...
}//catch

We can also write:

catch (Exception ex) {
    Console.WriteLine("The following error occurred: {0}", ex);
    ...
}//catch

The compiler will assign the value ex.ToString() to the parameter {0}.

The following example shows an exception generated by using a non-existent array element:


using System;

namespace Chap1 {
    class P08 {
        static void Main(string[] args) {
            // declaration & initialization of an array
            int[] tab = { 0, 1, 2, 3 };
            int i;
            // Display the array using a for loop
            for (i = 0; i < tab.Length; i++)
                Console.WriteLine("tab[{0}]={1}", i, tab[i]);
            // Display the array using a for-each loop
            foreach (int elmt in tab) {
                Console.WriteLine(item);
            }
            // Generate an exception
            try {
                tab[100] = 6;
            } catch (Exception e) {
                Console.Error.WriteLine("The following error occurred: " + e);
                return;
            }//try-catch
            finally {
                Console.WriteLine("finally ...");
            }
        }
    }
}

In the code above, line 18 will throw an exception because the tab array does not have an element at index 100. Running the program produces the following results:

tab[0]=0
tab[1]=1
tab[2]=2
tab[3]=3
0
1
2
3
The following error occurred: System.IndexOutOfRangeException: The index is out of bounds.
   at Chap1.P08.Main(String[] args) in C:\data\work\2007-2008\c# 2008\poly\Chap1\08\Program.cs:line 7
finally ...
  • line 9: the exception [System.IndexOutOfRangeException] occurred
  • line 11: the finally block (lines 23–25) of the code was executed, even though line 21 contained a return statement to exit the method. Note that the finally block is always executed.

Here is another example where we handle the exception caused by assigning a string to an integer variable when the string does not represent an integer:


using System;

namespace Chap1 {
    class P08 {
        static void Main(string[] args) {

            // Example 2
            // We ask for the name
            Console.Write("Name: ");
            // read response
            string name = Console.ReadLine();
            // Ask for age
            int age = 0;
            bool ageOK = false;
            while (!ageOK) {
                // question
                Console.Write("age: ");
                // read and verify response
                try {
                    age = int.Parse(Console.ReadLine());
                    ageOK = age >= 1;
                } catch {
                }//try-catch
                if (!ageOK) {
                    Console.WriteLine("Incorrect age, please try again...");
                }
            }//while
            // final output
            Console.WriteLine("Your name is {0} and you are {1} years old", name, age);
        }
    }
}
  • lines 15–27: the loop for entering a person’s age
  • line 20: the text entered via the keyboard is converted to an integer using the int.Parse method. This method throws an exception if the conversion is not possible. That is why the operation has been placed within a try/catch block.
  • lines 22-23: if an exception is thrown, we go into the catch block where nothing is done. Thus, the boolean ageOK, set to false on line 14, will remain false.
  • Line 21: If we reach this line, it means the string-to-int conversion was successful. However, we verify that the resulting integer is greater than or equal to 1.
  • Lines 24–26: An error message is displayed if the age is incorrect.

Some execution results:

1
2
3
Name: dupont
Age: 23
Your name is Dupont and you are 23 years old
1
2
3
4
5
6
7
Name: Durand
Age: x
Incorrect age, please try again...
Age: -4
Incorrect age, please try again...
Age: 12
Your name is Durand and you are 12 years old

3.6. Example Application - V1

We propose to write a program to calculate a taxpayer’s tax. We consider the simplified case of a taxpayer who has only their salary to report (2004 figures for 2003 income):

  • We calculate the number of tax brackets for the employee: nbParts = nbEnfants / 2 + 1 if they are unmarried, nbEnfants / 2 + 2 if they are married, where nbEnfants is the number of children.
  • if they have at least three children, they receive an additional half-share
  • We calculate their taxable income R = 0.72 * S, where S is their annual salary
  • We calculate the family coefficient QF = R / nbParts
  • We calculate the tax I. Consider the following table:
4262
0
0
8382
0.0683
291.09
14,753
0.1914
1,322.92
23,888
0.2826
2,668.39
38,868
0.3738
4,846.98
47,932
0.4262
6,883.66
0
0.4809
9505.54

Each row has 3 fields. To calculate tax I, find the first row where QF <= field1. For example, if QF = 5000, the row found will be

    8382        0.0683        291.09

Tax I is then equal to 0.0683*R - 291.09*nbParts. If QF is such that the condition QF<=field1 is never met, then the coefficients from the last row are used. Here:

0                0.4809    9505.54

which gives tax I = 0.4809*R - 9505.54*nbParts.

The corresponding C# program is as follows:


using System;

namespace Chap1 {
    class Taxes {
        static void Main(string[] args) {
            // data arrays needed to calculate the tax
            decimal[] limits = { 4962M, 8382M, 14753M, 23888M, 38868M, 47932M, 0M };
            decimal[] coeffR = { 0M, 0.068M, 0.191M, 0.283M, 0.374M, 0.426M, 0.481M };
            decimal[] coeffN = { 0M, 291.09M, 1322.92M, 2668.39M, 4846.98M, 6883.66M, 9505.54M };

            // retrieve marital status
            bool OK = false;
            string response = null;
            while (!OK) {
                Console.Write("Are you married (Y/N)?");
                response = Console.ReadLine().Trim().ToLower();
                if (response != "y" && response != "n")
                    Console.Error.WriteLine("Incorrect answer. Please try again");
                else OK = true;
            }//while
            bool marie = answer == "y";

            // number of children
            OK = false;
            int numChildren = 0;
            while (!OK) {
                Console.Write("Number of children: ");
                try {
                    nbChildren = int.Parse(Console.ReadLine());
                    OK = nbChildren >= 0;
                } catch {
                }// try
                if (!OK) {
                    Console.WriteLine("Incorrect answer. Please try again");
                }
            }// while

            // salary
            OK = false;
            int salary = 0;
            while (!OK) {
                Console.Write("Annual salary: ");
                try {
                    salary = int.Parse(Console.ReadLine());
                    OK = salary >= 0;
                } catch {
                }// try
                if (!OK) {
                    Console.WriteLine("Incorrect answer. Please try again");
                }
            }// while

            // Calculate the number of parts
            decimal numShares;
            if (married) nbParts = (decimal)nbChildren / 2 + 2;
            else nbParts = (decimal)nbChildren / 2 + 1;
            if (numberOfChildren >= 3) numberOfShares += 0.5M;

            // taxable income
            decimal income = 0.72M * salary;

            // family quotient
            decimal QF = income / nbParts;

            // find the tax bracket corresponding to QF
            int i;
            int nbBrackets = limits.Length;
            limits[nbTranches - 1] = QF;
            i = 0;
            while (QF > limits[i]) i++;
            // tax
            int taxes = (int)(coeffR[i] * income - coeffN[i] * nbShares);

            // display the result
            Console.WriteLine("Tax due: {0} euros", taxes);
        }
    }
}
  • lines 7–9: Numeric values are suffixed with M (Money) so that they are of type decimal.
  • line 16:
  • Console.ReadLine() returns the string C1 typed on the keyboard
  • C1.Trim() removes leading and trailing spaces from C1—returns a string C2
  • C2.ToLower() returns the string C3, which is the string C2 converted to lowercase.
  • line 21: the boolean variable marie is assigned the value true or false based on the condition reponse=="o"
  • Line 29: The string typed on the keyboard is converted to an int. If the conversion fails, an exception is thrown.
  • line 30: the boolean OK receives the value true or false from the condition nbEnfants>=0
  • Lines 55-56: We cannot simply write nbEnfants/2. If nbEnfants were equal to 3, we would have 3/2, an integer division that would result in 1 rather than 1.5. Therefore, we write (decimal)nbEnfants to make one of the operands of the division a real number and thus perform a division between real numbers.

Here are some examples of execution:

Are you married (Y/N)? y
Number of children: 2
Annual salary: 60,000
Tax due: 4,282 euros
Are you married (Y/N)? yes
Incorrect answer. Please try again
Are you married (Y/N)? Y
Number of children: three
Incorrect answer. Please try again
Number of children: 3
Annual salary: 60,000 euros
Incorrect answer. Please try again
Annual income: 60,000
Tax due: 2,959 euros

3.7. Main program arguments

The main function Main can accept an array of strings as a parameter: String[] (or string[]). This array contains the command-line arguments used to launch the application. So if we launch program P with the following (DOS) command:

        P arg0 arg1 … argn

and if the Main function is declared as follows:

public static void Main(string[] args)

we will have args[0]="arg0", args[1]="arg1" … Here is an example:


using System;

namespace Chap1 {
    class P10 {
        static void Main(string[] args) {
            // list the received parameters
            Console.WriteLine("There are  " + args.Length + " arguments");
            for (int i = 0; i < args.Length; i++) {
                Console.Out.WriteLine("arguments[" + i + "]=" + args[i]);
            }
        }
    }
}

To pass arguments to the executed code, proceed as follows:

  • in [1]: right-click on the project / Properties
  • in [2]: [Debug] tab
  • in [3]: enter the arguments

Execution yields the following results:

1
2
3
4
5
There are 4 arguments
arguments[0]=a0
arguments[1]=a1
arguments[2]=a2
arguments[3]=a3

Note that the signature

public static void Main()

is valid if the Main function does not expect any parameters.

3.8. Enumerations

An enumeration is a data type whose value range is a set of integer constants. Consider a program that needs to handle exam grades. There would be five: Passable, Fairly Good, Good, Very Good, Excellent.

We could then define an enumeration for these five constants:


        enum Grades { Passable, AssezBien, Bien, TrèsBien, Excellent };

Internally, these five constants are encoded as consecutive integers starting with 0 for the first constant, 1 for the next, and so on. A variable can be declared to take values from the enumeration:


            // a variable that takes its values from the Grades enumeration
Mentions myGrade = Mentions.Passable;

You can compare a variable to the different possible values of the enumeration:


            if (myRating == Mentions.Passable) {
                Console.WriteLine("Could do better");
}

You can retrieve all the values of the enumeration:


            // list of grades as strings
            foreach (Grade m in Enum.GetValues(myGrade.GetType())) {
                Console.WriteLine(m);
}

Just as the simple type int is equivalent to the System.Int32* structure, the simple enum type is equivalent to the System.Enum structure. This structure has a static method, GetValues, which retrieves all the values of an enumerated type passed as a parameter. This parameter must be an object of type Type, which is a class that provides information about a data type. The type of a variable v is obtained via v.GetType(). The type of a type T is obtained via typeof(T). So here, maMention.GetType() returns the Type object for the Mentions enumeration, and Enum.GetValues(maMention.GetType()) returns the list of values for the Mentions* enumeration.

If we now write


            //list of mentions as integers
            foreach (int m in Enum.GetValues(typeof(Mentions))) {
                Console.WriteLine(m);
}

Line 2: the loop variable is of type integer. We then obtain the list of enumeration values as integers. The System.Type object corresponding to the Mentions data type is obtained via typeof(Mentions). We could have written it as before: maMention.GetType().

The following program illustrates what has just been described:


using System;

namespace Chap1 {
    class P11 {
        enum Mentions { Passable, AssezBien, Bien, TrèsBien, Excellent };
        static void Main(string[] args) {
            // a variable that takes its values from the Mentions enumeration
            Mentions maMention = Mentions.Passable;
            // display variable value
            Console.WriteLine("grade=" + myGrade);
            // check against the enumeration value
            if (myGrade == Grades.Passable) {
                Console.WriteLine("Could do better");
            }
            // List of entries as strings
            foreach (Mentions m in Enum.GetValues(maMention.GetType())) {
                Console.WriteLine(m);
            }
            // list of mentions as integers
            foreach (int m in Enum.GetValues(typeof(Mentions))) {
                Console.WriteLine(m);
            }
        }
    }
}

The execution results are as follows:

rating=Fair
Could do better
Fair
Fairly Good
Good
Very Good
Excellent
0
1
2
3
4

3.9. Passing Parameters to a Function

Here we are interested in how parameters are passed to a function. Consider the following static function:


        private static void ChangeInt(int a) {
            a = 30;
            Console.WriteLine("Formal parameter a=" + a);
}

In the function definition, line 1, a is called a formal parameter. It is there solely for the purposes of defining the ChangeInt function. It could just as easily have been named b. Now let’s consider an example of using this function:


        public static void Main() {
            int age = 20;
            ChangeInt(age);
            Console.WriteLine("Actual parameter age=" + age);
}

Here, in the statement on line 3, ChangeInt(age), age is the actual parameter that will pass its value to the formal parameter a. We are interested in how a formal parameter retrieves the value of an actual parameter.

3.9.1. Pass-by-value

The following example shows that function parameters are passed by value by default, meaning that the value of the actual parameter is copied into the corresponding formal parameter. These are two distinct entities. If the function modifies the formal parameter, the actual parameter remains unchanged.


using System;

namespace Chap1 {
    class P12 {
        public static void Main() {
            int age = 20;
            ChangeInt(age);
            Console.WriteLine("Actual age parameter = " + age);
        }
        private static void ChangeInt(int a) {
            a = 30;
            Console.WriteLine("Formal parameter a=" + a);
        }
    }
}

The results are as follows:

Formal parameter a=30
Actual parameter age=20

The value 20 of the actual parameter age was copied into the formal parameter a (line 10). This was then modified (line 11). The actual parameter remained unchanged. This passing method is suitable for function input parameters.

3.9.2. Pass-by-reference

In a pass-by-reference, the actual parameter and the formal parameter are one and the same entity. If the function modifies the formal parameter, the actual parameter is also modified. In C#, both must be preceded by the ref keyword:

Here is an example:


using System;

namespace Chap1 {
    class P12 {
        public static void Main() {
            // example 2
            int age2 = 20;
            ChangeInt2(ref age2);
            Console.WriteLine("Actual parameter age2=" + age2);
        }
        private static void ChangeInt2(ref int a2) {
            a2 = 30;
            Console.WriteLine("Formal parameter a2=" + a2);
        }
    }
}

and the execution results:

Formal parameter a2=30
Actual parameter age2=30

The actual parameter followed the change in the formal parameter. This passing mode is suitable for a function's output parameters.

3.9.3. Passing by reference with the out keyword

Consider the previous example in which the variable age2 would not be initialized before the call to the changeInt function:


using System;

namespace Chap1 {
    class P12 {
        public static void Main() {
            // example 2
            int age2;
            ChangeInt2(ref age2);
            Console.WriteLine("Actual parameter age2=" + age2);
        }
        private static void ChangeInt2(ref int a2) {
            a2 = 30;
            Console.WriteLine("Formal parameter a2=" + a2);
        }
    }
}

When compiling this program, an error occurs:

    Use of unassigned local variable 'age2'

You can work around this by assigning an initial value to age2. You can also replace the ref keyword with the out keyword. This indicates that the parameter is solely an output parameter and therefore does not need an initial value:


using System;

namespace Chap1 {
    class P12 {
        public static void Main() {
            // example 3
            int age3;
            ChangeInt3(out age3);
            Console.WriteLine("Actual parameter age3=" + age3);
        }
        private static void ChangeInt3(out int a3) {
            a3 = 30;
            Console.WriteLine("Formal parameter a3=" + a3);
        }
    }
}

The results of the execution are as follows:

Formal parameter a3=30
Actual parameter age3=30