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:
- integers
- floating-point numbers
- decimal numbers
- characters and strings
- Booleans
- objects
3.2.1. Predefined data types
C# type | Type .NET | Data represented | Suffix of literal values | Encoding | Value range |
Char (S) | character | 2 bytes | Unicode character (UTF-16) | ||
String (C) | character string | reference to a Unicode character sequence | |||
Int32 (S) | integer | 4 bytes | [-231, 231-1] [–2147483648, 2147483647] | ||
UInt32 (S) | .. | U | 4 bytes | [0, 232-1] [0, 4294967295] | |
Int64 (S) | .. | L | 8 bytes | [-263, 263 -1] [–9223372036854775808, 9223372036854775807] | |
UInt64 (S) | .. | UL | 8 bytes | [0, 264 -1] [0, 18446744073709551615] | |
.. | 1 byte | [-27 , 27 -1] [-128,+127] | |||
Byte(s) | .. | 1 byte | [0 , 28 -1] [0,255] | ||
Int16 (S) | .. | 2 bytes | [-215, 215-1] [-32768, 32767] | ||
UInt16 (S) | .. | 2 bytes | [0, 216-1] [0,65535] | ||
Single (S) | floating-point | F | 4 bytes | [1.5 10-45, 3.4 10+38] in absolute value | |
Double (S) | .. | D | 8 bytes | [-1.7 10+308, 1.7 10+308] in absolute value | |
Decimal (S) | decimal number | M | 16 bytes | [1.0 10-28,7.9 10+28] as an absolute value with 28 significant digits | |
Boolean (S) | .. | 1 byte | true, false | ||
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:
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:
The display produces the types .NET and not the C# aliases.
3.2.2. Notation of literal data
145, -7, 0xFF (hexadecimal) | |
100000L | |
134.789, -45E-18 (-45 × 10⁻¹⁸) | |
134.789F, -45E-18F (-45 × 10⁻¹⁸) | |
100000M | |
'A', 'b | |
"today" "c:\\chap1\\paragraph3" @"c:\chap1\paragraph3" | |
true, false | |
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:
For example:
Why declare constants?
- The program will be easier to read if the constant is given a meaningful name:
- 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:
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:
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:
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:
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
number.ToString() | |
int.Parse(string) or System.Int32.Parse | |
long.Parse(string) or System.Int64.Parse | |
double.Parse(string) or System.Double.Parse(string) | |
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:
Note that real numbers in string form must use a comma, not a decimal point. Thus, we write
but
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:
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:
or more simply:
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:
or more simply:
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:
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.
![]() |
-
Reading information from the keyboard
-
processing information
-
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:
The Console class provides a ReadLine method associated by default with the In stream. We can therefore write:
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:
- 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:
- standard input device - defaults to the keyboard and is numbered 0
- standard output device - defaults to the screen and is numbered 1
- 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:
After the arguments argi of the pg program, you can redirect the standard I/O devices to files:
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. | |
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 | |
Same as above, but the written data is appended to the current contents of the file out.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 | |
Same as above, but the written data is appended to the current contents of the file 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]:
- 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
is valid. Due to precedence, the rightmost = operator is evaluated. We therefore have
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:
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:
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:
square root | |
Cosine | |
Sine | |
Tangent | |
x to the power of y (x > 0) | |
Exponential | |
Natural logarithm | |
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:
The complete definition of the Math class is as follows:





3.3.5.3. Operators in Arithmetic Expression Evaluation
The operator precedence when evaluating an arithmetic expression is as follows (from highest to lowest):
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.
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:
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
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:
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:
- !
- &&
- ||
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.
shifts i n bits to the left. The incoming bits are zeros. | |
shifts i n bits to the right. If i is a signed integer (signed char, int, long), the sign bit is preserved. | |
performs a bitwise logical AND of i and j. | |
performs a bitwise logical OR of i and j. | |
complements i to 1 | |
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:
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
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
gd | |
dg | |
dg | |
gd | |
gd | |
gd | |
gd | |
gd | |
gd | |
gd | |
gd | |
gd | |
gd | |
dg | |
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:
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:
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.
will terminate the program with a status value of 0.
3.4.2. Simple decision structure
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:
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#
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:
which can be translated into a while structure:
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:
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:
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:
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:
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
exits the for, while, or do...while loop. | |
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:
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:
- 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:
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
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) ? 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:
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:
Note that the signature
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:
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:
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:
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:
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:





