Skip to content

3. The Basics of the C# Language

3.1. Introduction

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

  • data
  • the instructions that manipulate it

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
Type .NET
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 Unicode character sequence
int
Int32 (S)
integer
 
4 bytes
[-231, 231-1] [–2147483648, 2147483647]
uint
UInt32 (S)
..
U
4 bytes
[0, 232-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 -1] [-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)
floating-point
F
4 bytes
[1.5 10-45, 3.4 10+38] in absolute value
double
Double (S)
..
D
8 bytes
[-1.7 10+308, 1.7 10+308] in absolute value
decimal
Decimal (S)
decimal number
M
16 bytes
[1.0 10-28,7.9 10+28] as an absolute value with 28 significant digits
bool
Boolean (S)
..
 
1 byte
true, false
object
Object (C)
object reference
  
object reference

Above, we have listed the C# types alongside their equivalent .NET types, with the comment (S) if the type is a structure 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 type int is a C# type. Int32 is a structure belonging to the System namespace. Its full name is therefore System.Int32. The type int is a C# alias that refers to the structure .NET System.Int32. Similarly, the C# type string 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 nomDuType=3.GetType().FullName;

Above, literal 3 is by default of type C# int, and therefore of type .NET System.Int32. This structure has a method GetType() 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 = "essai";
            uint ui = 5;
            long l = 1000;
            ulong ul = 1001;
            byte octet = 5;
            short sh = -4;
            ushort ush = 10;
            decimal dec = 10.67M;
            bool b = true;
            Console.WriteLine("Type de ent[{1}] : [{0},{2}]", ent.GetType().FullName, ent,sizeof(int));
            Console.WriteLine("Type de fl[{1}]: [{0},{2}]", fl.GetType().FullName, fl, sizeof(float));
            Console.WriteLine("Type de d[{1}] : [{0},{2}]", d.GetType().FullName, d, sizeof(double));
            Console.WriteLine("Type de s[{1}] : [{0}]", s.GetType().FullName, s);
            Console.WriteLine("Type de ui[{1}] : [{0},{2}]", ui.GetType().FullName, ui, sizeof(uint));
            Console.WriteLine("Type de l[{1}] : [{0},{2}]", l.GetType().FullName, l, sizeof(long));
            Console.WriteLine("Type de ul[{1}] : [{0},{2}]", ul.GetType().FullName, ul, sizeof(ulong));
            Console.WriteLine("Type de b[{1}] : [{0},{2}]", octet.GetType().FullName, octet, sizeof(byte));
            Console.WriteLine("Type de sh[{1}] : [{0},{2}]", sh.GetType().FullName, sh, sizeof(short));
            Console.WriteLine("Type de ush[{1}] : [{0},{2}]", ush.GetType().FullName, ush, sizeof(ushort));
            Console.WriteLine("Type de dec[{1}] : [{0},{2}]", dec.GetType().FullName, dec, sizeof(decimal));
            Console.WriteLine("Type de 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 using v.GetType().FullName. The size of a structure S can be obtained using sizeof(S). The statement Console.WriteLine("... {0} ... {1} ...", param0, param1, ...) prints the text specified as its first parameter to the screen, replacing each {i} notation with the value of the expression parami.
  • Line 22: Since the type string denotes a class and not a structure, the sizeof operator cannot be used.

Here is the result of the execution:

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

The display produces the types .NET and not the C# aliases.

3.2.2. Notation of literal data

entier int (32 bits)
145, -7, 0xFF (hexadecimal)
entier long (64 bits) - suffixe L
100000L
réel double
134.789, -45E-18 (-45 × 10⁻¹⁸)
réel float (suffixe F)
134.789F, -45E-18F (-45 × 10⁻¹⁸)
réel decimal (suffixe M)
100000M
caractère char
'A', 'b
chaîne de caractères string
"today" "c:\\chap1\\paragraph3" @"c:\chap1\paragraph3"
booléen bool
true, false
date
new DateTime(1954,10,13) (year, month, day) for 10/13/1954

Note the two literal strings: "c:\\chap1\\paragraph3" and @"c:\chap1\paragraph3". In literal strings, 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 incorrect if x is a string, for example.

3.2.3.2. Declaration of Constants

The syntax for declaring a constant is as follows:

    const type nom=valeur;          //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 taux_tva=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 change needed will be to modify the statement defining its value:
    const float taux_tva=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:

 Identificateur_de_type variable1[=valeur1],variable2=[valeur2],...;

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

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

 var variable1=valeur1,variable2=valeur2,...;

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 de int i=2 : {0},{1}",i.GetType().Name,i.GetType().FullName);
            var j = 3;
            Console.WriteLine("Type de var j=3 : {0},{1}", j.GetType().Name, j.GetType().FullName);
            var aujourdhui = DateTime.Now;
            Console.WriteLine("Type de var aujourdhui : {0},{1}", aujourdhui.GetType().Name, aujourdhui.GetType().FullName);
        }
    }
}
  • line 6: an explicitly typed data type
  • 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 item. Because 3 is of type int, j will be of type int.
  • Line 10: an implicitly typed data item. Because DateTime.Now is of type DateTime, today will be of type DateTime.

At runtime, we get the following result:

1
2
3
Type de int i=2 : Int32,System.Int32
Type de var j=3 : Int32,System.Int32
Type de var aujourdhui : 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 aujourdhui = "aujourd'hui";

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

nombre -> chaîne
number.ToString()
chaine -> int
int.Parse(string) or System.Int32.Parse
chaîne -> long
long.Parse(string) or System.Int64.Parse
chaîne -> double
double.Parse(string) or System.Double.Parse(string)
chaîne -> 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. In this case, a fatal error called an exception is generated. This error can be handled by the following try/catch block:

try{
    appel de la fonction susceptible de générer l'exception
} catch (Exception e){
    traiter l'exception e
}
instruction suivante

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. 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
            affiche(i.ToString());
            affiche(l.ToString());
            affiche(f.ToString());
            affiche(d.ToString());
 
            //boolean --> string
            const bool b = false;
            affiche(b.ToString());
 
            // string --> int
            int i1;
            i1 = int.Parse("10");
            affiche(i1.ToString());
            try {
                i1 = int.Parse("10.67");
                affiche(i1.ToString());
            } catch (Exception e) {
                affiche("Erreur : " + e.Message);
            }
 
            // string --> long
            long l1;
            l1 = long.Parse("100");
            affiche(l1.ToString());
            try {
                l1 = long.Parse("10.675");
                affiche(l1.ToString());
            } catch (Exception e) {
                affiche("Erreur : " + e.Message);
            }
 
            // chain --> double
            double d1;
            d1 = double.Parse("100,87");
            affiche(d1.ToString());
            try {
                d1 = double.Parse("abcd");
                affiche(d1.ToString());
            } catch (Exception e) {
                affiche("Erreur : " + e.Message);
            }
 
            // string --> float
            float f1;
            f1 = float.Parse("100,87");
            affiche(f1.ToString());
            try {
                d1 = float.Parse("abcd");
                affiche(f1.ToString());
            } catch (Exception e) {
                affiche("Erreur : " + e.Message);
            }
 
        }// fine hand
 
        public static void affiche(string S) {
            Console.Out.WriteLine("S={0}",S);
        }
    }// end of class
}

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

The results obtained are as follows:

S=10
S=100000
S=45,78
S=-14,98
S=False
S=10
S=Erreur : Input string was not in a correct format.
S=100
S=Erreur : Input string was not in a correct format.
S=100,87
S=Erreur : Input string was not in a correct format.
S=100,87
S=Erreur : 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[] tableau[]=new Type[n]

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

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

or more simply:

    int[] entiers={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[,] réels=new double[,] { {0.5, 1.7}, {8.4, -6}};

or more simply:

    double[,] réels={ {0.5, 1.7}, {8.4, -6}};

The number of elements in each dimension can be obtained using the method GetLength(i), 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 tableau[i] is a one-dimensional array reference. These references tableau[i] are not initialized in the declaration above. Their value is the null reference.

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


            // a table of tables
            string[][] noms = new string[3][];
            for (int i = 0; i < noms.Length; i++) {
                noms[i] = new string[i + 1];
            }//for
            // initialization
            for (int i = 0; i < noms.Length; i++) {
                for (int j = 0; j < noms[i].Length; j++) {
                    noms[i][j] = "nom" + 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 {
    // tables
 
    using System;
 
    // test class
    public class P02 {
        public static void Main() {
            // an initialized 1-dimensional array
            int[] entiers = new int[] { 0, 10, 20, 30 };
            for (int i = 0; i < entiers.Length; i++) {
                Console.Out.WriteLine("entiers[{0}]={1}", i, entiers[i]);
            }//for
 
            // an initialized 2-dimensional array
            double[,] réels = new double[,] { { 0.5, 1.7 }, { 8.4, -6 } };
            for (int i = 0; i < réels.GetLength(0); i++) {
                for (int j = 0; j < réels.GetLength(1); j++) {
                    Console.Out.WriteLine("réels[{0},{1}]={2}", i, j, réels[i, j]);
                }//for j
            }//for i
 
            // a table of tables
            string[][] noms = new string[3][];
            for (int i = 0; i < noms.Length; i++) {
                noms[i] = new string[i + 1];
            }//for
            // initialization
            for (int i = 0; i < noms.Length; i++) {
                for (int j = 0; j < noms[i].Length; j++) {
                    noms[i][j] = "nom" + i + j;
                }//for j
            }//for i
            // display
            for (int i = 0; i < noms.Length; i++) {
                for (int j = 0; j < noms[i].Length; j++) {
                    Console.Out.WriteLine("noms[{0}][{1}]={2}", i, j, noms[i][j]);
                }//for j
            }//for i
        }//Main
    }//class
}//namespace

When executed, we get the following results:

entiers[0]=0
entiers[1]=10
entiers[2]=20
entiers[3]=30
réels[0,0]=0,5
réels[0,1]=1,7
réels[1,0]=8,4
réels[1,1]=-6
noms[0][0]=nom00
noms[1][0]=nom10
noms[1][1]=nom11
noms[2][0]=nom20
noms[2][1]=nom21
noms[2][2]=nom22

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 write operations (Write, WriteLine). The Console class has two properties, Out and Error, which are write streams of type TextWriter:

  • 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 ligne=Console.In.ReadLine();

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

    string ligne=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 Out feed
            object obj = new object();
            Console.Out.WriteLine(obj);
 
            // write to Error stream
            int i = 10;
            Console.Error.WriteLine("i=" + i);
 
            // read a line entered on the keyboard
            Console.Write("Tapez une ligne : ");
            string ligne = Console.ReadLine();
            Console.WriteLine("ligne={0}", ligne);
        }//fine hand
    }//end of class
}
  • Line 9: obj is an object reference
  • line 10: obj is printed to the screen. By default, the method obj.ToString() 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 expression n.

The result of the execution is as follows:

1
2
3
4
System.Object
i=10
Tapez une ligne : je suis là
ligne=je suis là
  • line 1: the output produced by line 10 of the code. The method obj.ToString() displayed the type name of the variable obj: System.Object. The object type is a C# alias for the .NET System.Object type.

3.3.4. I/O Redirection

Under 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 - defaults to the screen and is numbered 1
  3. standard error device—by default refers to the screen and is numbered 2

In C#, the write stream Console.Out writes to device 1, the write stream Console.Error writes to device 2, and the read stream Console.In 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 stream Console.In will therefore take its data from the file in.txt.
1>out.txt
redirects output stream #1 to the file out.txt. This means that in the program, the stream Console.Out 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 file out.txt.
2>error.txt
Redirects output #2 to the file error.txt. This means that in the program, the Console.Error stream will write its data to the file error.txt
2>>error.txt
Same as above, but the written data is appended to the current contents of the file error.txt.
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 In stream
            string data = Console.In.ReadLine();
            // write Out feed
            Console.Out.WriteLine("écriture dans flux Out : " + data);
            // write stream Error
            Console.Error.WriteLine("écriture dans flux Error : " + 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 was created in the project's bin/Release folder.

Let's 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
écriture dans flux Out : test
...\04\bin\Release>more err.txt
écriture dans flux Error : test
  • line 1: insert the string "test" into the file in.txt
  • Lines 2-3: Display the contents of the file in.txt for verification
  • line 4: executing the 04.exe program. 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. Execution does not produce any output.
  • Lines 5-6: contents of the out.txt file. These contents show us that:
  • the file in.txt was read
  • the screen output was redirected to out.txt
  • Lines 7-8: Similar verification for the file err.txt

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 rightmost = operator 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 these functions are defined in a C# class called Math. When using them, you must prefix them with the name of the class where they are defined. Thus, 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 Expression Evaluation

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

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

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, and *true otherwise.

      bool fin;
      int x=...;
      fin=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:

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

    "Chat" < "Chaton".

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 <, <=, >, and >=, you must use the <span id="stringcompareto" name="stringcompareto" class="odt-bookmark-anchor"></span> * method of the *System.CompareTo class:


using System;
 
namespace Chap1 {
    class P05 {
        static void Main(string[] args) {
            string chaine1="chat", chaine2="chien";
            int n = chaine1.CompareTo(chaine2);
            bool egal = chaine1.Equals(chaine2);
            Console.WriteLine("i={0}, egal={1}", n, egal);
            Console.WriteLine("chien==chaine1:{0},chien!=chaine2:{1}", "chien"==chaine1,"chien" != chaine2);
        }
    }
}

Line 7, the variable i will have the value:

0 if the two strings are equal

1 if string #1 > string #2

-1 if string #1 < 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
chien==chaine1:False,chien!=chaine2:False

3.3.5.5. Boolean expressions

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

Operator precedence:

  1. !
  2. &&
  3. ||

            double x = 3.5;
            bool valide = 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 logical OR of i and j.
~i
complements i to 1
i^j
performs the OU EXCLUSIF 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 notation variable-- means variable=variable-1 or variable-=1.

3.3.5.9. The ternary operator ?

The expression

    expr_cond ? expr1:expr2

is evaluated as follows:

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

2 If 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

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

gd indicates that, given equal precedence, left-to-right precedence is observed. This means that when operators of the same precedence appear in an expression, 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) valeur

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-type real number and a int-type integer. It is the division between real numbers that is then performed. The value of j will also be converted to type float, and then the division of the two real 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 exactly encoded on 2 bytes
  • (float) i is the same value encoded as an approximate 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 type int.

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.

syntaxe        void Exit(int status)
action        arrête le processus en cours et rend la valeur status au processus père

Exit terminates the current process 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 decision structure

 syntaxe :  if (condition) {actions_condition_vraie;} else {actions_condition_fausse;}

notes:

  • The condition is enclosed in parentheses.
  • Each action is terminated by a semicolon.
  • Curly braces are not followed by a semicolon.
  • Curly braces are only required 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_sinon;
            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

                selon la valeur de choix
                    cas 0
                         fin du module
                    cas 1
                         exécuter module M1
                    cas 2
                        exécuter module M2
                    sinon
                         erreur<--real
                findescas

In C#

int choix = 2; 
bool erreur = false;
switch (choix) {
   case 0: return;
   case 1: M1(); break;
   case 2: M2(); break;
   default: erreur = true; break;
}
}// end 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 loop:

pour i variant de id à if avec un pas de ip
    actions
finpour

which can be translated into a while structure:

    i  id
    tantque i<=if
        actions
        i i+ip
    fintantque

foreach structure

The syntax is as follows:


foreach (Type variable 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 the values of the collection.

Thus, the following code:


            string[] amis = { "paul", "hélène", "jacques", "sylvie" };
            foreach (string nom in amis) {
                Console.WriteLine(nom);
         }

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:

tantque condition
    actions
fintantque

Do-while loop

The syntax is as follows:


    do{
       instructions;
    }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:

répéter
    actions
jusqu'provided

Structure for general (for)

The syntax is as follows:


for(instructions_départ;condition;instructions_fin_boucle){
    instructions; 
}

The loop continues as long as the condition is true (evaluated before each iteration). Instructions_départ are executed before entering the loop for the first time. Instructions_fin_boucle are executed after each iteration.

Notes

  • The various instructions in instructions_depart and instructions_fin_boucle are separated by commas.

The corresponding algorithmic structure is as follows:

instructions_départ
tantque condition
    actions
    instructions_fin_boucle
fintantque

Examples

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

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

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

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

            i = 1; somme = 0;
            do somme += 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 susceptible de générer une exception
} catch (Exception e){
    traiter l'exception e
}
instruction suivante

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 multiple types of exceptions, we may want to be more specific by handling the exception with multiple catch blocks:
try{
    code susceptible de générer les exceptions
} catch ( IndexOutOfRangeException e1){
    traiter l'exception e1
}
} catch ( FormatException e2){
    traiter l'exception e2
}
instruction suivante
  • You can add a finally clause to try/catch blocks:
try{
    code susceptible de générer une exception
} catch (Exception e){
    traiter l'exception e
}
finally{
    code exécuté après try ou catch
}
instruction suivante

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 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("L'erreur suivante s'est produite : {0}",ex.Message);
    ...
}//catch
  • The Exception class has a method ToString that returns a string indicating the exception type and the value of the Message property. We can therefore write:
catch (Exception ex){
    Console.WriteLine("L'erreur suivante s'est produite : {0}", ex.ToString());
    ...
}//catch

We can also write:

catch (Exception ex){
    Console.WriteLine("L'erreur suivante s'est produite : {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) {
            // declaring & initializing an array
            int[] tab = { 0, 1, 2, 3 };
            int i;
            // table display with for
            for (i = 0; i < tab.Length; i++)
                Console.WriteLine("tab[{0}]={1}", i, tab[i]);
            // affichage tableau avec un for each
            foreach (int élmt in tab) {
                Console.WriteLine(élmt);
            }
            // generating an exception
            try {
                tab[100] = 6;
            } catch (Exception e) {
                Console.Error.WriteLine("L'erreur suivante s'est produite : " + 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
L'the following error occurred: System.IndexOutOfRangeException: The index is outside the limits of the table.
   à Chap1.P08.Main(String[] args) dans C:\data\travail\2007-2008\c# 2008\poly\Chap1\08\Program.cs:ligne 7
finally ...
  • line 9: exception [System.IndexOutOfRangeException] occurred
  • line 11: the finally clause (lines 23–25) of the code was executed, even though line 21 contained a return statement to exit the method. Note that the finally clause 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("Nom : ");
            // reading response
            string nom = Console.ReadLine();
            // age requested
            int age = 0;
            bool ageOK = false;
            while (!ageOK) {
                // question
                Console.Write("âge : ");
                // reading-checking response
                try {
                    age = int.Parse(Console.ReadLine());
                    ageOK = age>=1;
                } catch {
                }//try-catch
                if (!ageOK) {
                    Console.WriteLine("Age incorrect, recommencez...");
                }
            }//while
            // final display
            Console.WriteLine("Vous vous appelez {0} et vous avez {1} an(s)",nom,age);
        }
    }
}
  • lines 15–27: the loop for entering a person’s age
  • line 20: the line typed on the keyboard is converted to an integer by the int.Parse method. This method throws an exception if the conversion is not possible. That is why the operation has been placed in a try/catch block.
  • lines 22-23: if an exception is thrown, we enter 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 conversion string -> int was successful. However, we verify that the resulting integer is indeed 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
Nom : dupont
âge : 23
Vous vous appelez dupont et vous avez 23 an(s)
1
2
3
4
5
6
7
Nom : durand
âge : x
Age incorrect, recommencez...
âge : -4
Age incorrect, recommencez...
âge : 12
Vous vous appelez durand et vous avez 12 an(s)

3.6. Sample 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 a single 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 he is married, where nbEnfants is the number of his children.
  • if he has at least three children, he has an additional half-share
  • his taxable income is calculated as R=0.72*S, where S is his annual salary
  • we calculate his family coefficient QF = R / nbParts
  • We calculate his 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, we look for the first row where QF <= field1. For example, if QF = 5000, we will find the row

    8382        0.0683        291.09

Tax I is then equal to 0.0683*R - 291.09*nbParts. If QF is such that the relationship QF<=field1 is never satisfied, then the coefficients of the last line are used. Here:

0 0.4809 9505.54

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

The corresponding C# program is as follows:


using System;
 
namespace Chap1 {
    class Impots {
        static void Main(string[] args) {
            // data tables required for tax calculation
            decimal[] limites = { 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 };
 
            // we recover marital status
            bool OK = false;
            string reponse = null;
            while (!OK) {
                Console.Write("Etes-vous marié(e) (O/N) ? ");
                reponse = Console.ReadLine().Trim().ToLower();
                if (reponse != "o" && reponse != "n")
                    Console.Error.WriteLine("Réponse incorrecte. Recommencez");
                else OK = true;
            }//while
            bool marie = reponse == "o";
 
            // number of children
            OK = false;
            int nbEnfants = 0;
            while (!OK) {
                Console.Write("Nombre d'enfants : ");
                try {
                    nbEnfants = int.Parse(Console.ReadLine());
                    OK = nbEnfants >= 0;
                } catch {
                }// try
                if (!OK) {
                    Console.WriteLine("Réponse incorrecte. Recommencez");
                }
            }// while
 
            // salary
            OK = false;
            int salaire = 0;
            while (!OK) {
                Console.Write("Salaire annuel : ");
                try {
                    salaire = int.Parse(Console.ReadLine());
                    OK = salaire >= 0;
                } catch {
                }// try
                if (!OK) {
                    Console.WriteLine("Réponse incorrecte. Recommencez");
                }
            }// while
 
            // calculating the number of shares
            decimal nbParts;
            if (marie) nbParts = (decimal)nbEnfants / 2 + 2;
            else nbParts = (decimal)nbEnfants / 2 + 1;
            if (nbEnfants >= 3) nbParts += 0.5M;
 
            // taxable income
            decimal revenu = 0.72M * salaire;
 
            // family quotient
            decimal QF = revenu / nbParts;
 
            // search for tax bracket corresponding to QF
            int i;
            int nbTranches = limites.Length;
            limites[nbTranches - 1] = QF;
            i = 0;
            while (QF > limites[i]) i++;
            // tax
            int impots = (int)(coeffR[i] * revenu - coeffN[i] * nbParts);
 
            // the result is displayed
            Console.WriteLine("Impôt à payer : {0} euros", impots);
        }
    }
}
  • 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 the 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 entered via the keyboard is converted to type int. If the conversion fails, an exception is thrown.
  • line 30: the boolean OK is set to true or false based on 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 and not 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:

Etes-vous marié(e) (O/N) ? o
Nombre d'children: 2
Salaire annuel : 60000
Impôt à payer : 4282 euros
Etes-vous marié(e) (O/N) ? oui
Réponse incorrecte. Recommencez
Etes-vous marié(e) (O/N) ? o
Nombre d'children: three
Réponse incorrecte. Recommencez
Nombre d'children: 3
Salaire annuel : 60000 euros
Réponse incorrecte. Recommencez
Salaire annuel : 60000
Impôt à payer : 2959 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 parameters received
            Console.WriteLine("Il y a  " + 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
Il y a  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,AssezBien,Good,TrèsBien, Excellent.

We could then define an enumeration for these five constants:


        enum Mentions { 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 this enumeration:


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

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


            if (maMention == Mentions.Passable) {
                Console.WriteLine("Peut mieux faire");
}

You can retrieve all the values of the enumeration:


            // list of terms in string form
            foreach (Mentions m in Enum.GetValues(maMention.GetType())) {
                Console.WriteLine(m);
}

Just as the simple type int is equivalent to the structure System.Int32, the simple enum type is equivalent to the structure System.Enum. This structure has a static method GetValues that retrieves all values of an enumerated type passed as a parameter. This parameter must be an object of type Type, which is a class containing information about a data type. The type of a variable v is obtained using v.GetType(). The type of a type T is obtained via typeof(T). Therefore, 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 in integer form
            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 object of type System.Type 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;
            // variable value display
            Console.WriteLine("mention=" + maMention);
            // test with enumeration value
            if (maMention == Mentions.Passable) {
                Console.WriteLine("Peut mieux faire");
            }
            // list of terms in string form
            foreach (Mentions m in Enum.GetValues(maMention.GetType())) {
                Console.WriteLine(m);
            }
            //list of mentions in integer form
            foreach (int m in Enum.GetValues(typeof(Mentions))) {
                Console.WriteLine(m);
            }
        }
    }
}

The execution results are as follows:

mention=Passable
Peut mieux faire
Passable
AssezBien
Bien
TrèsBien
Excellent
0
1
2
3
4

3.9. Passing Parameters to a Function

Here we will look at how parameters are passed to a function. Consider the following static function:


        private static void ChangeInt(int a) {
            a = 30;
            Console.WriteLine("Paramètre formel a=" + a);
}

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


        public static void Main() {
            int age = 20;
            ChangeInt(age);
            Console.WriteLine("Paramètre effectif 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("Paramètre effectif age=" + age);
        }
        private static void ChangeInt(int a) {
            a = 30;
            Console.WriteLine("Paramètre formel a=" + a);
        }
    }
}

The results are as follows:

Paramètre formel a=30
Paramètre effectif 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("Paramètre effectif age2=" + age2);
        }
        private static void ChangeInt2(ref int a2) {
            a2 = 30;
            Console.WriteLine("Paramètre formel a2=" + a2);
        }
    }
}

and the execution results:

Paramètre formel a2=30
Paramètre effectif age2=30

The actual parameter followed the change in the formal parameter. This passing method 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 function changeInt:


using System;
 
namespace Chap1 {
    class P12 {
        public static void Main() {
            // example 2
            int age2;
            ChangeInt2(ref age2);
            Console.WriteLine("Paramètre effectif age2=" + age2);
        }
        private static void ChangeInt2(ref int a2) {
            a2 = 30;
            Console.WriteLine("Paramètre formel 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("Paramètre effectif age3=" + age3);
        }
        private static void ChangeInt3(out int a3) {
            a3 = 30;
            Console.WriteLine("Paramètre formel a3=" + a3);
        }
    }
}

The results of the execution are as follows:

Paramètre formel a3=30
Paramètre effectif age3=30