Skip to content

3. C# language basics

3.1. Introduction

We'll start by treating C# as a classical programming language. We'll look at classes later. There are two things in a program:

  • data
  • the instructions that handle them

We generally try to separate data from instructions:

3.2. C# data

C# uses the following data types:

  1. whole numbers
  1. real numbers
  2. decimal numbers
  3. characters and strings
  4. booleans
  5. objects

3.2.1. Predefined data types

C# type
Type .NET
Represented data
Suffix
literal values
Coding
Value domain
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)
whole number
 
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)
real number
F
4 bytes
[1.5 10-45, 3.4 10+38in absolute terms
double
Double (S)
..
D
8 bytes
[-1.7 10+308, 1.7 10+308in absolute terms
decimal
Decimal (S)
decimal number
M
16 bytes
[1.0 10-28,7.9 10+28] in absolute value with 28 significant digits
bool
Boolean (S)
..
 
1 byte
true, false
object
Object (C)
object reference
  
object reference

Above, C# types are indicated by their equivalent .NET type, with the comment (S) if the type is a structure and (C) if the type is a class. On discovered 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. Its full name is System.Int32. The type int is a C# alias for the structure .NET System.Int32. Similarly, the C# string is an alias for type .NET System.String. System.String is a class and not a structure. The two concepts are similar, but with the following fundamental difference:

  • a variable of type Structure can be manipulated via its value
  • a variable of type Class can be manipulated via its address (reference in object language).

A structure as a class are complex types with attributes and methods. For example, we can write :

string nomDuType=3.GetType().FullName;

Above is the literal 3 is C# by default int, type .NET System.Int32. This structure has a GetType() which renders an object encapsulating the characteristics of the data type System.Int32. These include the property FullName returns the full name of the type. As you can see, the literal 3 is more complex than it first appears.

Here is a program illustrating these 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 by v.GetType().FullName. The size of an S structure can be obtained by sizeof(S). Instruction Console.WriteLine("... {0} ... {1} ...",param0, param1, ...) writes to the screen the text that is its first parameter, replacing each notation with {i} by the value of the expression parami.
  • line 22: the type string operator cannot be used, as it designates a class and not a structure sizeof.

Here's the result:

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 .NET types, not 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-18)
réel float (suffixe F)
134.789F, -45E-18F (-45 10-18)
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 13/10/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 mark and not the succession of the two characters \ and n. If we wanted this succession, we'd 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 written. This is sometimes called a string verbatim.

3.2.3. Data reporting

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 translated, the compiler assigns each data item a memory location characterized by an address and a size. It does this with the help of declarations made by the programmer.

They also enable the compiler to detect programming errors. For example, the

x=x*2;

will be declared erroneous 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;          //définit constante nom=valeur

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" changes. For example, in the previous case, if the vat rate changes to 33%, the only modification to be made will be to modify the instruction defining its value:
    const float taux_tva=0.33F;

If 0.186 had been used explicitly in the program, many instructions would have had to be modified.

3.2.3.3. Variable declaration

A variable is identified by a name and refers to a data type. C# is case-sensitive. Thus FIN and end 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 either a predefined type or a type defined by the programmer. Optionally, a variable can be initialized as well as declared.

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

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

The key word var doesn't mean that variables don't have a specific type. The variable variablei a the data type valuei assigned to it. Initialization is ici mandatory so that the compiler can deduce the variable's type.

Here's 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: explicitly typed data
  • line 7: (data).GetType().Name is the short name for (data), (donnée).GetType().FullName is the full name of (data)
  • line 8: implicitly typed data. Because 3 is of type int, j will be of type int.
  • line 10: implicitly typed data. Because DateTime.Now type is DateTime, today type will be DateTime.

On execution, we obtain 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 keyword var cannot then change type. For example, after line 10 of the code, the line :


            var aujourdhui = "aujourd'hui";

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

3.2.4. Conversions between numbers and strings

nombre -> chaîne
nombre.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. A fatal error called exception. This error can be handled by the try/catch next :


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

If the function doesn't generate an exception, we go to following instruction, otherwise we go to the body of the clause catch then to following instruction. We'll come back to exception handling later. Here's a program demonstrating some techniques for converting between numbers and strings. In this example, the function poster writes the value of its parameter to the screen. Thus poster(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);
            }
 
             // chain --> 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 the possible exception that can occur. e.Message is the error message linked to the exception e.

The results 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 the comma and not the decimal point. Thus, we write

double d1=10.7; 

but

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

3.2.5. Data tables

A C# array is an object used to group data of the same type under the same identifier. Its declaration is as follows:

Type[] tableau[]=new Type[n]

n is the number of data items the array can contain. The syntax Table[i] designates data no i where i belongs to the interval [0,n-1]. Any reference to data Table[i] where i does not belong to the interval [0,n-1] will trigger an exception. An array can be initialized as well as declared:

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

or simply :

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

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

A two-dimensional picture can be declared as follows:

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

where n is the number of lines, m number of columns. The syntax Table[i,j] designates element j in line i of table. The two-dimensional array can also be initialized at the same time as it is declared:

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

or simply :

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

The number of elements in each dimension can be obtained using the GetLength(i) where i=0 represents the dimension corresponding to the 1st index, i=1 the dimension corresponding to the 2nd index, ..

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

A table of tables is declared as follows:

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

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

The following example illustrates the creation of an array of tables:


            // un tableau de tableaux
            string[][] noms = new string[3][];
            for (int i = 0; i < noms.Length; i++) {
                noms[i] = new string[i + 1];
            }//for
            // initialisation
            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: a table names of 3 string[][]. Each element is an array pointer (an object reference) whose elements are of type string[].
  • lines 3-5: the 3 elements of the table names are initialized. Each now "points" to an array of elements of type string[]. names[i][j] is the j type table string [] referenced by names[i].
  • line 9: element initialization names[i][j] inside a double loop. Ici names[i] is an array of i+1 elements. As names[i] is a painting, names[i].Length is its number of elements.

Here is an example of the three types of table 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

On execution, we obtain 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# instructions

A distinction is made between

1 the elementary instructions executed by the computer.

2 instructions for controlling the program sequence.

The elementary instructions become clear when you consider the structure of a microcomputer and its peripherals.

  1. reading information from the keyboard

  2. information processing

  3. writing information on screen

3.3.1. Writing on screen

There are different screen writing instructions:

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

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

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

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

The flows Out and Error can be redirected to text files at program runtime, as we'll see shortly.

3.3.2. Reading typed data

The data stream from the keyboard is designated by the object Console.In type TextReader. This type of object can be used to read a line of text using the ReadLine :

    string ligne=Console.In.ReadLine();

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

    string ligne=Console.ReadLine();

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

3.3.3. Input-output example

Here's a short program to illustrate keyboard/screen I/O 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 the Error stream
            int i = 10;
            Console.Error.WriteLine("i=" + i);
 
             // reading 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 written to the screen. By default, the obj.ToString() which is called.
  • line 14: you can also write :

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

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

The result is as follows:

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

3.3.4. I/O redirection

Under DOS and UNIX there are three standard devices called :

  1. standard input device - defaults to the keypad and is numbered 0
  2. standard output device - defaults to the display and is numbered 1
  3. standard error device - defaults to the screen and is numbered 2

In C#, the Console.Out writes to device 1, the write stream Console.Error is written to device 2 and the read stream Console.In reads data from device 0.

When you run a program under Dos or Unix, you can set which devices 0, 1 and 2 will be used for the executed program. Consider the following command line:

pg arg1 arg2 .. argn

Behind the arguments argi program pg, standard I/O devices can be redirected to files:

0<in.txt
standard input stream no. 0 is redirected to the in.txt. In the program, the Console.In will therefore take its data from the in.txt.
1>out.txt
redirects output no. 1 to the file out.txt. This means that in the program the Console.Out will write its data to the out.txt
1>>out.txt
ditto, but the written data is added to the current file contents out.txt.
2>error.txt
redirects output no. 2 to the file error.txt. This means that in the program the Console.Error will write its data to the error.txt
2>>error.txt
ditto, but the written data is added to the current file contents error.txt.
1>out.txt 2>error.txt
Devices 1 and 2 are both redirected to

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


using System;
 
namespace Chap1 {
 
     // redirections
    public class P04 {
        public static void Main(string[] args) {
            // lecture flux In
            string data = Console.In.ReadLine();
             // write Out feed
            Console.Out.WriteLine("écriture dans flux Out : " + data);
            // écriture flux Error
            Console.Error.WriteLine("écriture dans flux Error : " + data);
         }//Main
     }//class
}

Let's generate the executable of 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 bin/Release of the project.

Let's issue 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: the string test in the in.txt
  • lines 2-3: display file contents in.txt for verification
  • line 4: program execution 04.exe. The flow In is redirected to the in.txt, the flow Out to the out.txt, the flow Error to the err.txt. Execution does not cause any display.
  • lines 5-6: file contents out.txt. This content shows us that :
  • the file in.txt has been read
  • the screen display has been redirected to out.txt
  • lines 7-8: similar check for the file err.txt

We can clearly see that flows Out and In do not write to the same devices, as they have been redirected separately.

3.3.5. Assigning the value of an expression to a variable

We are interested ici in the operation variable=expression;

The expression can be of type : arithmetic, relational, Boolean, characters

3.3.5.1. Interpretation of the assignment operation

The operation variable=expression;

is itself a expression whose evaluation takes place as follows:

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

This is how the

    V1=V2=expression

is legal. Because of the priority, the rightmost = operator will be evaluated. We therefore have

    V1=(V2=expression)

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

    V1=V

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

So the V1=V2=expression

is an expression whose evaluation

  • causes the value of expression to variables V1 and V2
  • results in the value of expression.

This can be generalized to an expression like :

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 whole quotient. Thus 5/2 -> 2 and 5.0/2 ->2.5.

% division: the result is the remainder, whatever the nature of the operands, the quotient being a whole number. It is therefore the operation modulo.

There are various mathematical functions. Here are some of them:

double Sqrt(double x)
square root
double Cos(double x)
Cosinus
double Sin(double x)
Sinus
double Tan(double x)
Tangent
double Pow(double x,double y)
x to the power y (x>0)
double Exp(double x)
Exponential
double Log(double x)
Neperian logarithm
double Abs(double x)
absolute value

etc...

All these functions are defined in a C# class called Math. When used, they must be prefixed with the name of the class in which they are defined. For example, write :

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

The complete definition of the Math is as follows:

Image

Image

Image

Image

Image

3.3.5.3. Priorities for evaluating arithmetic expressions

The priority of operators when evaluating an arithmetic expression is as follows (from highest to lowest priority):

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

Operators in the same [ ] block have the same priority.

3.3.5.4. Relational expressions

The operators are as follows:

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

operator priorities

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

The result of a relational expression is the Boolean false if expression is false, true or else.

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

Comparison of two characteristics

Let's consider two characters C1 and C2. They can be compared using the operators

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

Their Unicode codes, which are numbers, are then compared. In Unicode order, we have the following relationships:

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

Comparing two strings

They are compared character by character. The first inequality encountered between two characters induces an inequality of the same meaning on the strings.

Examples :

Either compare the "Cat" and "Dog" strings

This last inequality means that "Cat" < "Dog".

Either compare the "Cat" and "Kitten" chains. There is a tie all the time until the "Cat" chain is exhausted. In this case, the exhausted chain is declared the "smallest". We therefore have the relationship

    "Chat" < "Chaton".

Functions for comparing two strings

You can use the relational operators == and != to test equality of two strings, or the Equals class System.String. For < <= > >= relationships, use the CompareTo class System.String :


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 both chains are equal

    1    if channel 1 &gt; channel 2

    -1    if chain n°1 &lt; chain n°2

Line 8, the variable egal will have the value true if both chains are equal, false otherwise. Line 10 uses the == and != operators to check whether two strings are equal or not.

Execution results :

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

  1. !
  2. &&
  3. ||

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

Relational operators have priority operators && and ||.

3.3.5.6. Bit processing

Operators

Let be i and j 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 the logical ET of i and j bit by bit.
i | j
performs the logical OU of i and j bit by bit.
~i
complements i to 1
i^j
makes the OU EXCLUSIF of i and j

Or 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));
  • format {0:x4} displays parameter n° 0 in hexadecimal format (x) with 4 characters (4).

The results 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. Operator combination

a=a+b peut s'écrire a+=b

a=a-b peut s'écrire a-=b

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

3.3.5.8. Increment and decrement operators

Scoring variable++ means variable=variable+1 or even variable+=1

Scoring variable-- means variable=variable-1 or even 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. It is a conditional expression with a real or fake

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

3 If 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 be assigned to variable i : j+1 if j>4, j-1 or else. It's the same as writing if(j>4) i=j+1; else i=j-1; but it's more concise.

3.3.5.10. General priority for operators

() []  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 for equal priority, left-right priority is observed. This means that when there are operators of equal priority in an expression, the leftmost operator in the expression is evaluated first. dg indicates right-left priority.

3.3.5.11. Type changes

In an expression, you can momentarily change the encoding of a value. This is known as changing the data type or type casting. The syntax for changing the type of a value in an expression is next:

    (type) valeur

The value then assumes the indicated type. This leads to a change in the encoding of the value.


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 3/4 division is an integer division since both operands are of type int.
  • line 8, (float)i is the value of i transformed into float. Now we have a division between a real of type float and an integer of type int. This is the division between real numbers. The value of j will also be transformed into a float, then divide the two reals. f2 will then have the value 0.75.

Here are the results:

f1=0, f2=0,75

In the (float)i :

  • i is an exact 2-byte coded value
  • (float) i is the same value encoded as a real 4-byte approximation

The value of i. This transcoding only takes place for the duration of a calculation, and the variable i always retaining its type int.

3.4. Program flow control instructions

3.4.1. Stop

The Exit defined in the Environment stops program execution.

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 it. Under DOS, this status variable is rendered in the system variable ERRORLEVEL whose value can be tested in a batch file. Under Unix, with the Shell Bourne command interpreter, the variable $? which retrieves the value of status.

    Environment.Exit(0);

will stop program execution with a status value of 0.

3.4.2. Simple choice structure

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

notes:

  • the condition is enclosed in brackets.
  • each action is terminated by a semicolon.
  • braces are not terminated by semicolons.
  • braces are only necessary if there is more than one action.
  • the clause else may be absent.
  • there is no clause then.

The algorithmic equivalent of this structure is the if .. then ... otherwise :

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, the else of line 10 refers to which if ? The rule is that a else always refers to the if nearest : if(n>6), line 8, in the example. Consider another example:


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

Ici we wanted to put a else at if(n2>1) and no else at if(n2>6). Because of the previous remark, we are obliged to put braces on the 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 can be an integer, a character, a string
  • the expression is enclosed in brackets.
  • the clause default may be absent.
  • the values vi are possible values of the expression. If the value of the expression is vi , the actions behind the clause case vi are executed.
  • instruction break takes us out of the case structure.
  • each instruction block linked to a value vi must end with a branch instruction (break, goto, return, ...) otherwise the compiler reports an error.

example

Visit 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<--vrai
                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. Repeat structures

3.4.4.1. Known number of repetitions

Structure for

The syntax is as follows:


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

Notes

  • the 3 arguments of for are enclosed in a parenthesis and separated by semicolons.
  • every for is terminated by a semicolon.
  • the brace is only necessary if there is more than one action.
  • the brace is not followed by a semicolon.

The algorithmic equivalent is the for :

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

which can be translated as a as :

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

Structure foreach

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 already know 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 which will successively take as its value all the values in the collection.

Thus the following code :


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

would show :

paul
hélène
jacques
sylvie

3.4.4.2. Number of repetitions unknown

There are many structures in C# for this case.

Structure tantque (while)


    while(condition){
          actions;
        } 

We loop as long as the condition is verified. The loop may never be executed.

notes:

  • the condition is enclosed in brackets.
  • each action is terminated by a semicolon.
  • the brace is only necessary if there is more than one action.
  • the brace is not followed by a semicolon.

The corresponding algorithmic structure is tantque :

tantque condition
    actions
fintantque

Repeat structure until (do while)

The syntax is as follows:


    do{
       instructions;
    }while(condition); 

We loop until the condition becomes false. Ici the loop is made at least once.

notes

  • the condition is enclosed in brackets.
  • each action is terminated by a semicolon.
  • the brace is only necessary if there is more than one action.
  • the brace is not followed by a semicolon.

The corresponding algorithmic structure is repeat ... until :

répéter
    actions
jusqu'à condition

Structure for general (for)

The syntax is as follows:


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

We loop as long as the condition is true (evaluated before each loop turn). Start_instructions are performed before entering the loop for the first time. Instructions_fin_boucle are executed after each loop.

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 fragments 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 management instructions

break
exits for loop, while, do ... while.
continue
advances the for, while, do ... loops to the next iteration. while

3.5. Exception handling

Many C# functions are likely to generate exceptions, i.e. errors. When a function is likely to generate an exception, the programmer should manage it with the aim of obtaining more error-resistant programs: you must always avoid the savage "crash" of an application.

An exception is handled as follows:

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

If the function doesn't generate an exception, we go to following instruction, otherwise we go to the body of the clause catch then to following instruction. Note the following points :

  • e is an object of type Exception or derivative. You can be more precise by using types such as IndexOutOfRangeException, FormatException, SystemException, etc.: there are several types of exception. By writing catch (Exception e), indicates that you want to handle all exception types. If the code in the try is likely to generate several types of exception, you may want to be more precise by managing the exception with several catch :
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
  • We can add to the clauses try/catch, a clause finally :
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 there is an exception or not, the clause code finally will always be executed.

  • In the catch, you may not want to use the Exception available. Instead of writing catch (Exception e){..}, we write catch(Exception){...} or simply catch {...}.
  • The class Exception has a property Message which is a message detailing the error that has occurred. So if we want to display this one, we'll write :
catch (Exception ex){
    Console.WriteLine("L'erreur suivante s'est produite : {0}",ex.Message);
    ...
}//catch
  • The class Exception has a method ToString which returns a string indicating the type of exception and the value of the Message. We can thus 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 {0} parameter the value ex.ToString().

The following example shows an exception generated by the use of 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]);
             // table display with a 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 ...");
            }
        }
    }
}

Above, line 18 will generate an exception because the array tab has no element no. 100. Running the program gives the following results:

tab[0]=0
tab[1]=1
tab[2]=2
tab[3]=3
0
1
2
3
L'erreur suivante s'est produite : System.IndexOutOfRangeException: L'index se trouve en dehors des limites du tableau.
   à 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 clause finally (lines 23-25) of the code was executed, even though line 21 contained an instruction return to exit the method. Note that the finally is always executed.

Here's another example of how to 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 : ");
                 // read-verify answer
                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 transformed into an integer using the int.Parse. This method throws an exception if conversion is not possible. This is why the operation has been placed in a try / catch.
  • lines 22-23: if an exception is thrown, we go to the catch where nothing is done. Thus, the Boolean ageOK positioned at false, line 14, will it remain false.
  • line 21: if you reach this line, the conversion string -> int has been successful. However, check that the integer obtained is greater than or equal to 1.
  • lines 24-26: an error message is issued if the age is incorrect.

Some performance 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. Application example - V1

We propose to write a program to calculate a taxpayer's income tax. The simplified case is that of a taxpayer with only his salary to declare (2004 figures for 2003 income):

  • the number of employee shares is calculated nbParts=nbEnfants/2 +1 if unmarried, nbEnfants/2+2 if married, where nbEnfants is its number of children.
  • if he has at least three children, he gets half a share more
  • calculate your taxable income R=0.72*S where S is his annual salary
  • calculate your family coefficient QF=R/nbParts
  • calculate your tax I. Consider the following table :
4262
0
0
8382
0.0683
291.09
14753
0.1914
1322.92
23888
0.2826
2668.39
38868
0.3738
4846.98
47932
0.4262
6883.66
0
0.4809
9505.54

Each line has 3 fields. To calculate tax I, look for the first line where QF<=champ1. For example, if QF=5000 we find the line

    8382        0.0683        291.09

Tax I is then equal to 0.0683*R - 291.09*nbParts. If QF is such that the relation QF<=champ1 is never checked, then the coefficients of the last line are used. Ici :

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 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 };
 
             // marital status is restored
            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: numerical values are suffixed with M (Money) to be of type decimal.
  • line 16:
    • Console.ReadLine() makes the chain C1 typed
    • C1.Trim() removes leading and trailing spaces C1 - renders a string C2
    • C2.ToLower() makes the chain C3 which is the chain C2 transformed into lower case.
  • line 21: the Boolean marie receives the value true or false relationship answer=="o"
  • line 29: the string typed on the keyboard is transformed into a int. If the transformation fails, an exception is thrown.
  • line 30: the Boolean OK receives the value true or false relationship nbEnfants>=0
  • lines 55-56: you can't simply write nbEnfants/2. If nbEnfants was equal to 3, we'd have 3/2, an integer division that would give 1 and not 1.5. So we write (decimal)nbEnfants to make one of the operands of the division real and thus have a division between reals.

Here are some examples:

Etes-vous marié(e) (O/N) ? o
Nombre d'enfants : 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'enfants : trois
Réponse incorrecte. Recommencez
Nombre d'enfants : 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 table contains the command line arguments used to launch the application. For example, if we run program P with the following command (Dos) :


        P arg0 arg1 … argn

and if the Main is declared as follows:

public static void Main(string[] args)

args[0]="arg0", args[1]="arg1" ... Here's 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]: put the arguments

Execution gives 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

public static void Main()

is valid if the Main expects no parameters.

3.8. Enumerations

An enumeration is a data type whose value domain is a set of integer constants. Let's consider a program that has to manage the grades of an exam. There would be five of them: Fair,AssezBien,Fine,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 by consecutive integers starting with 0 for the first constant, 1 for the next, etc... A variable can be declared as taking these values in the enumeration :


            // une variable qui prend ses valeurs dans l'énumération Mentions
Mentions maMention = Mentions.Passable;

A variable can be compared with the various possible values in the enumeration:


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

You can obtain all the values of the enumeration :


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

In the same way as the simple type int is equivalent to the System.Int32, the simple type enum is equivalent to the System.Enum. This structure has a static method GetValues which allows you to obtain all the values of an enumerated type that you is passed as a parameter. This must be an object of type Type which is a class of information about the type of data. The type of a variable v is obtained by v.GetType(). The type of type T is obtained by typeof(T). So ici maMention.GetType() gives object Type enumeration Mentions and Enum.GetValues(maMention.GetType()) the list of enumeration values Mentions.

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 integer type. We then obtain the list of enumeration values in integer form. The object of type System.Type corresponding to the data type Mentions is obtained by typeof(Mentions). We could have writtennt, maMention.GetType().

The following program highlights what has just been written:


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

We're interested ici in the way parameters are passed through 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, line1, a is called a formal parameter. It is there only for the purposes of defining the changeInt. It might as well have been called b. Let's now consider one use of this function:


        public static void Main() {
            int age = 20;
            ChangeInt(age);
            Console.WriteLine("Paramètre effectif age=" + age);
}

Ici in the instruction on line 3, ChangeInt(age), age is the effective parameter which will transmit its value to the formal parameter a. We're interested in how a formal parameter retrieves the value of an actual parameter.

3.9.1. Passage by value

The following example shows that, by default, function parameters are passed by value, i.e. the value of the actual parameter is copied into the corresponding formal parameter. We have two distinct entities. If the function modifies the formal parameter, the effective 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 effective parameter age has been copied into the formal parameter a (line 10). This was then modified (line 11). The actual parameter remained unchanged. This mode is suitable for function input parameters.

3.9.2. Passage by reference

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

Here's 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 performance results :

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

The effective parameter has followed the modification of the formal parameter. This mode is suitable for function output parameters.

3.9.3. Passage by reference with the out keyword

Consider the previous example in which the variable age2 function would not be initialized before calling the 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 we compile this program, we get an error :

    Use of unassigned local variable 'age2'

We can get around this obstacle by assigning an initial value to age2. You can also replace the keyword ref with the keyword out. We then express that the parameter is only 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 are as follows:

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