Introduction to C# |
A complete PDF version of the text book is now available. The PDF version is an almost complete subset of the HTML version (where only a few, long program listings have been removed). See here. |
As already mentioned in Chapter 1, this material is primarily targeted at people who know the C programming language. With this outset, we do not need to dwell on issues such as elementary types, operators, and control structures. The reason is that C and C# (and Java for that sake) are similar in these respects.
In this chapter we will discuss the aspects of C# which have obvious counterparts in C. Hopefully, the chapter will be helpful to C programmers who have an interest in C# programming.
In this chapter 'C#' refers to C# version 2.0. When we discuss C we refer to ANSI C ala 1989.
|
6.1. Simple types
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
C supports the simple types char, bool, int, float and double. In addition there are a number of variation of some of these. In this context, we will also consider pointers as simple types.
The major differences between C# and C with respect to simple types are the following:
|
In C it is not possible to tell the bit sizes of the simple types. In some C implementations an int, for instance, will made by 4 bytes (32 bits), but in other C implementations an int may be longer or shorter. In C# (as well as in Java) the bit sizes of the simple types are defined and fixed as part of the specification of the language.
In C there is no boolean type. Boolean false is represented by zero values (such as integer 0) and boolean true is represented by any other integer value (such as the integer 1). In C# there is a boolean type, named bool, that contain the natural values denoted by true and false.
The handling of characters is messy in C. Characters in C are supported by the type named char. The char type is an integer type. There is a great deal of confusion about signed and unsigned characters. Typically, characters are represented by 8 bits in C, allowing for representation of the extended ASCII alphabet. In C# the type char represent 16 bits characters. In many respects, the C# type char corresponds to the Unicode alphabet. However, 16 bits are not sufficient for representation of all characters in the Unicode alphabet. The issue of character representation, for instance in text files, relative to the type char is a complex issue in C#. In this material it will be discussed in the lecture about IO, starting in Chapter 37. More specifically, you should consult Section 37.7.
The high-precision, 128 bit type called decimal is new in C#. It is a decimal floating point type (as opposed to float and double which are binary floating point types). Values in type decimal are intended for financial calculations. Internally, a decimal value consists of a sign bit (representing positive or negative), a 96 bit integer (mantissa) and a scaling factor (exponent) implicitly between 100 and 10-28. The 96 bit integer part allows for representation of (at least) 28 decimal digits. The decimal exponent allows you to set the decimal point anywhere in the 28 decimal number. The decimal type uses 3 * 4 = 12 bytes for the mantissa and 4 bytes for the exponent. (Not all bits in the exponent are used, however). For more information, see [decimal-floating-point].
C pointers are not intended to be used in C#. However, C pointers are actually supported in the part of C# known as the unsafe part of the language. The concept of references is very important in C#. References and pointers are similar, but there are several differences as well. Pointers and references will be contrasted and discussed below, in Section 6.5.
All simple types in C# are in reality represented as structs (but not all structs are simple types). As such, this classifies the simple types in C# as value types, as a contrast to reference types. In addition, in C#, this provides for definition of methods of simple types. Structs are discussed in Section 6.6.
Below we show concrete C# program fragments which demonstrate some aspects of simple types.
1 2 3 4 5 6 7 8 9 10 11 | using System; class BoolDemo{ public static void Main(){ bool b1, b2; b1 = true; b2 = default(bool); Console.WriteLine("The value of b2 is {0}", b2); // False } } | |||
|
In Program 6.1 we have emphasized the parts that relate to the type bool. We declare two boolean variables b1 and b2, and we initialize them in the line below their declarations. Notice the possibility of asking for the default value of type bool. This possibility also applies to other types. The output of Program 6.1 reveals that the default value of type bool is false.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | using System; class CharDemo{ public static void Main(){ char ch1 = 'A', ch2 = '\u0041', ch3 = '\u00c6', ch4 = '\u00d8', ch5 = '\u00c5', ch6; Console.WriteLine("ch1 is a letter: {0}", char.IsLetter(ch1)); Console.WriteLine("{0} {1} {2}", ch3, ch4, char.ToLower(ch5)); ch6 = char.Parse("B"); Console.WriteLine("{0} {1}", char.GetNumericValue('3'), char.GetNumericValue('a')); } } | |||
|
In Program 6.2 we demonstrate the C# type char. We declare a number of variables, ch1 ... ch6, of type char. ch1 ... ch5 are immediately initialized. Notice the use of single quote character notation, such as 'A'. This is similar to the notation used in C. Also notice the '\u....' escape notation. This is four digit unicode character notation. Each dot in '\u....' must be a hexadecimal digit between 0 and f (15). The unicode notation can be used to denote characters, which are not necessarily available on your keyboard, such as the Danish letters Æ, Ø and Å shown in Program 6.2. Notice also the char operations, such as char.IsLetter, which is applied on ch1 in the program. Technically, IsLetter is a static method in the struct Char (see Section 6.6 and Section 14.3 for an introduction to structs). There are many similar operations that classify characters. These operations correspond to the abstractions (macros) in the C library ctype.h. It is recommended that you - as an exercise - locate IsLetter in the C# library documentation. It is important that you are able to find information about already existing types in the documentation pages. See also Exercise 2.1.
In the program that demonstrated the type char we have seen examples of hexadecimal numbers. It is worthwhile to understand why hexadecimal numbers are used for these purposes. This side box is a crash course on number systems and hexadecimal numbers. The normal numbers are decimal, using base 10. The meaning of the number 123 is 1 * 102 + 2 * 101 + 3 * 100 The important observation is that we can use an arbitrary base b, b > 1 as an alternative to 10 for decomposition of a number. Base numbers which are powers
of 2 are particularly useful. If we use base 2 we get the binary numbers. Binary numbers
correspond directly to the raw digital representation used in computers. The binary notation
of 123 is 1111011 because 1 * 26 + 1 * 25 + 1 * 24 + 1 * 23 + 0 * 22 + 1 * 21 + 1 * 20 is equal to the decimal number 123.Binary numbers are important when we approach the lower level of a computer, but as can be seen above, binary numbers are unwieldy and not very practical. Hexadecimal numbers are used instead. Hexadecimal numbers use base 16, which is 24. We need 16 digits for notation of hexadecimal numbers. The first 10 digits are 0 .. 9. In lack of better notation, we use the letters A .. F as the six last digits. A = 10, ..., F = 15. The important observation is that a group of four binary digits (corresponding to four bits) can be translated to a single hexadecimal number. Thus, we can immediately translate the binary number 01111011 to the two hexadecimal digits 7 and 11. These two hexadecimal digits are denoted as 7 and B respectively. With this observation, at single byte of eight bits can written as exactly two hexadecimal digits. Grouping the bits of 1111011 leads to 0111 1011. 0111 is 7. 1011 is 11 which is denoted by the hexadecimal digit B. The hexadecimal number 7B means 7 * 16 1 + 11 * 16 0 which is 123 (in decimal notation).The explantion above is - in a nutshell - the reason why you should care about hexadecimal numbers. In Exercise 2.2 we will write programs that deal with hexadecimal numbers. |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | using System; using System.Globalization; class NumberDemo{ public static void Main(){ sbyte sb1 = sbyte.MinValue; // Signed 8 bit integer System.SByte sb2 = System.SByte.MaxValue; Console.WriteLine("sbyte: {0} : {1}", sb1, sb2); byte b1 = byte.MinValue; // Unsigned 8 bit integer System.Byte b2 = System.Byte.MaxValue; Console.WriteLine("byte: {0} : {1}", b1, b2); short s1 = short.MinValue; // Signed 16 bit integer System.Int16 s2 = System.Int16.MaxValue; Console.WriteLine("short: {0} : {1}", s1, s2); ushort us1 = ushort.MinValue; // Unsigned 16 bit integer System.UInt16 us2= System.UInt16.MaxValue; Console.WriteLine("ushort: {0} : {1}", us1, us2); int i1 = int.MinValue; // Signed 32 bit integer System.Int32 i2 = System.Int32.MaxValue; Console.WriteLine("int: {0} : {1}", i1, i2); uint ui1 = uint.MinValue; // Unsigned 32 bit integer System.UInt32 ui2= System.UInt32.MaxValue; Console.WriteLine("uint: {0} : {1}", ui1, ui2); long l1 = long.MinValue; // Signed 64 bit integer System.Int64 l2 = System.Int64.MaxValue; Console.WriteLine("long: {0} : {1}", l1, l2); ulong ul1 = ulong.MinValue; // Unsigned 64 bit integer System.UInt64 ul2= System.UInt64.MaxValue; Console.WriteLine("ulong: {0} : {1}", ul1, ul2); float f1 = float.MinValue; // 32 bit floating-point System.Single f2= System.Single.MaxValue; Console.WriteLine("float: {0} : {1}", f1, f2); double d1 = double.MinValue; // 64 bit floating-point System.Double d2= System.Double.MaxValue; Console.WriteLine("double: {0} : {1}", d1, d2); decimal dm1 = decimal.MinValue; // 128 bit fixed-point System.Decimal dm2= System.Decimal.MaxValue; Console.WriteLine("decimal: {0} : {1}", dm1, dm2); string s = sb1.ToString(), t = 123.ToString(); } } | |||
|
In Program 6.3 we show a program that demonstrates all numeric types in C#. For illustrative purposes, we use both the simple type names (such as int, shown in purple) and the underlying struct type names (such as System.Int32 shown in blue). To give you a feeling of the ranges of the types, the program prints the smallest and the largest value for each numeric type. At the bottom of Program 6.3 we show how the operation ToString can be used for conversion from a numeric type to the type string. The output of the numeric demo program is shown in Listing 6.4 (only on web).
1 2 3 4 5 6 7 8 9 10 11 | sbyte: -128 : 127 byte: 0 : 255 short: -32768 : 32767 ushort: 0 : 65535 int: -2147483648 : 2147483647 uint: 0 : 4294967295 long: -9223372036854775808 : 9223372036854775807 ulong: 0 : 18446744073709551615 float: -3,402823E+38 : 3,402823E+38 double: -1,79769313486232E+308 : 1,79769313486232E+308 decimal: -79228162514264337593543950335 : 79228162514264337593543950335 | |||
|
In Focus box 6.1 we studied hexadecimal numbers. We will now see how to deal with hexadecimal numbers in C#. A number prefixed with 0x is written in hexadecimal notation. Thus, 0x123 is equal to the decimal number 291. In C and Java the prefix 0 is used for octal notation. Thus, in C and Java 0123 is equal to the decimal number 83. This convention is not used in C#. In C#, 0123 is just a decimal number prefixed with a redundant digit 0. While prefixes are used for encoding of number systems, suffixes of number constants are used for encoding of numerical types. As an example, 0X123L denotes a hexadecimal constant of type long (a 64 bit integer). The following suffixes can be used for integer types: U (unsigned), L (long), and UL (unsigned long). The following suffixes can be used for real types: F (float), D (double), and M (decimal). Both lowercase and uppercase suffixes will work. A number can formatted in both decimal and hexadecimal notation. In the context of a Console.WriteLine call, the format specification (or placeholder) {i:X} will write the value of the variable i in hexadecimal notation. This is demonstrated by the following C# program: using System; class NumberDemo{ public static void Main(){ int i = 0123, j = 291; long k = 0X123L; Console.WriteLine("{0:X}", i); // 7B Console.WriteLine("{0:D}", i); // 123 Console.WriteLine("{0:X}", j); // 123 Console.WriteLine("{0:D}", k); // 291 } } In the program shown above, D means decimal and X means hexadecimal. Some additional formattings are also provided for numbers: C (currency notation), E (exponential notation), F (fixed point notation), G (Compact general notation), N (number notation), P (percent notation), R (round trip notation for float and double). You should consult the online documentation for additional explanations. |
Exercise 2.1. Exploring the type Char The type System.Char (a struct) contains a number of useful methods, and a couple of constants. Locate the type System.Char in your C# documentation and take a look at the methods available on characters. You may ask where you find the C# documentation. There are several possibilities. You can find it at the Microsoft MSDN web site at msdn.microsoft.com. It is also integrated in Visual Studio and - to some degree - in Visual C# express. It comes with the C# SDK, as a separate browser. It is also part of the documentation web pages that comes with Mono. If you are a Windows user I will recommend the Windows SDK Documentation Browser which is bundled with the C# SDK. Along the line of the character demo program above, write a small C# program that uses the char predicates IsDigit, IsPunctuation, and IsSeparator. It may be useful to find the code position - also known as the code point - of a character. As an example, the code position of 'A' is 65. Is there a method in System.Char which gives access to this information? If not, can you find another way to find the code position of a character? Be sure to understand the semantics (meaning) of the method GetNumericValue in type Char. Solution |
Exercise 2.2. Hexadecimal numbers In this exercise we will write a program that can convert between decimal and hexadecimal notation of numbers. Please consult the focus boxes about hexadecimal numbers in the text book version if you need to. You might expect that this functionality is already present in the C# libraries. And to some degree, it is. The static method ToInt32(string, Int32) in class Convert converts the string representation of a number (the first parameter) to an arbitrary number system (the second parameter). Similar methods exist for other integer types. The method ToString(string) in the struct Int32, can be used for conversion from an integer to a hexadecimal number, represented as a string. The parameter of ToString is a format string. If you pass the string "X" you get a hexadecimal number. The program below shows examples:
Now, write a method which converts a list (or array) of digits in base 16 (or more generally, base b, b >= 2) to a decimal number. The other way around, write a method which converts a positive decimal integer to a list (or array) of digits in base 16 (or more generally, base b). Here is an example where the requested methods are used:
|
6.2. Enumerations types
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
Enumeration types in C# are similar to enumeration types in C, but a number of extensions have been introduced in C#:
|
Below, in Program 6.5 we see that the enumeration type OnOff is based on the type byte. The enumeration type Ranking is - per default - based on int.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | using System; class NonSimpleTypeDemo{ public enum Ranking {Bad, OK, Good} public enum OnOff: byte{ On = 1, Off = 0} public static void Main(){ OnOff status = OnOff.On; Console.WriteLine(); Console.WriteLine("Status is {0}", status); Ranking r = Ranking.OK; Console.WriteLine("Ranking is {0}", r ); Console.WriteLine("Ranking is {0}", r+1); Console.WriteLine("Ranking is {0}", r+2); bool res1 = Enum.IsDefined(typeof(Ranking), 3); Console.WriteLine("{0} defined: {1}", 3, res1); bool res2= Enum.IsDefined(typeof(Ranking), Ranking.Good); Console.WriteLine("{0} defined: {1}", Ranking.Good , res2); bool res3= Enum.IsDefined(typeof(Ranking), 2); Console.WriteLine("{0} defined: {1}", 2 , res3); foreach(string s in Enum.GetNames(typeof(Ranking))) Console.WriteLine(s); } } | |||
|
In the example the methods IsDefined and GetNames are examples of static methods inherited from System.Enum.
In line 13 of Program 6.5 On is printed. In a similar C program, the number 1 would be printed.
In line 16 OK is printed, and line 17 prints Good. In line 18 the value of r + 2 is 3, which does not correspond to any of the values in type Ranking. Therefore the base value 3 is printed.
All the output of Program 6.5 is listed in Listing 6.6.
Combined enumeration (sometimes known as flags enumeration) is a slightly more advanced concept. We introduce it in Focus box 6.3.
1 2 3 4 5 6 7 8 9 10 | Status is On Ranking is OK Ranking is Good Ranking is 3 3 defined: False Good defined: True 2 defined: True Bad OK Good | |||
|
Let us point out some additional interesting details in Program 6.5. There are two enumeration types in the program, namely a type called Ranking and another called OnOff. When we declare variables, the types Ranking and OnOff are used via their names. C programmer will be relieved to find out that it is not necessary (and not possible) to write enum Ranking and enum OnOff. Thus, no C-like typedefs are necessary to introduce simple naming.
In order to disambiguate the referencing of constants in an enumeration type, dot notation ala Ranking.OK must always be used. In the same way as in C, the enumeration constants have associated an integer value. The operation IsDefined allows us to check if a given value belongs to an enumeration type. IsDefined is an operation (a method) in a struct called Enum.
As a very pleasant surprise for the C programmer, there is access to the names of enumeration constants from the program. We show in the program that the expressions Enum.GetNames(typeof(Ranking)) returns a string array with the elements "Bad", "OK", and "Good". In the same direction - as we have already seen above - it is possible to print the symbolic names of the enumeration constants. This is very useful. In C programs we always need a tedious switch to obtain a similar effect..
Combined enumerations can be used to deal with small sets symbolic constants. Here is an example: [Flags] public enum Direction: byte{ North = 1, East = 2, South = 4, West = 8, } The first thing to notice is the mapping of symbolic constants to powers of two. We can form an expression North | West which is the bitwise combination of the underlying integer values 1 and 8. | is a bitwise or operator, see Table 6.1. At the binary level, 1 | 8 is equivalent to 0001 | 1000 = 1001, which represents the number 9. You should think of 1001 as a bit array where the leftmost bit is the encoding of West and the rightmost bit is the encoding of North. [Flags] is an application of an attribute, see Section 39.6. It instructs the compiler to generate symbolic names of combinations, such as the composite name North, West in the example below. We can program with the enumeration type in the following way: Direction d = Direction.North | Direction.West; Console.WriteLine("Direction {0}", d); // Direction North, West Console.WriteLine("Direction {0}", (int)d); // 9 Console.WriteLine(HasDirection(d, Direction.North)); // True Console.WriteLine(HasDirection(d, Direction.South)); // False The method HasDirection is a method we have programmed in the following way: // Is d in the direction e public static bool HasDirection(Direction d, Direction e){ return (d & e) == e; } It checks if e is contained in d in a bitwise sense. It is also possible to name some of the combinations explicitly in the enumeration type: [Flags] public enum Direction: byte{ North = 1, East = 2, South = 4, West = 8, NorthWest = North | West, NorthEast = North | East, SouthWest = South | West, SouthEast = South | East } |
Exercise 2.3. ECTS Grades Define an enumeration type ECTSGrade of the grades A, B, C, D, E, Fx and F and associate the Danish 7-step grades 12, 10, 7, 4, 2, 0, and -3 to the symbolic ECTS grades. What is the most natural underlying type of ECTSGrade? Write a small program which illustrates how to use the new enumeration type. Solution |
Exercise 2.4. Use of Enumeration types Consult the documentation of type type System.Enum, and get a general overview of the methods in this struct. Be sure that you are able to find the documentation of System.Enum Test drive the example EnumTest, which is part of MicroSoft's documentation. Be sure to understand the program relative to its output. Write your own program with a simple enumeration type. Use the Enum.CompareTo method to compare two of the values of your enumeration type. Solution |
6.3. Non-simple types
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
The most important non-simple types are defined by classes and structs. These types define non-atomic objects/values. Because C# is a very rich language, there are other non-simple types as well, such as interfaces and delegates. We will not discuss these in this chapter, but they will play important roles in later chapters.
The most important similarities between C and C# with respect to non-simple types can be summarized in the following way:
|
The most important differences are:
|
A C programmer, who have experience with arrays, strings, and structs from C, will immediately feel comfortable with these types in C#. But such a C programmer will also quickly find out, that there are substantial new possibilities in C# that makes life easier.
Arrays and strings will be discussed in Section 6.4. Classes and structs are, basically, what the rest of the book is about. The story about classes starts in Chapter 10.
6.4. Arrays and Strings
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
Arrays and strings are both non-simple types that are well-known by C programmers. In C# both arrays and strings are defined by classes. As we will see later, this implies that arrays and strings are represented as objects, and they are accessed via references. This stands as a contrast to C# structs, which are values and therefore not accessed via references.
The syntax of array declaration and initialization is similar in C and C#. In C, a string is a pointer to a the first character in the string, and it is declared of the type char*. C# supports a type named string. The notation of string constants is also similar in C and C#, although C# offers additional possibilities (the @"..." notation, see below).
The following summarizes important differences in between C and C# with respect to arrays and strings:
|
A multi-dimensional array in C is constructed as an array in which the elements are themselves arrays. Such arrays are known as jagged arrays in C#, because not all constituent arrays need to have the same size. In addition C# supports a new kind of arrays, namely rectangular arrays (of two or more dimensions). Such arrays are similar to arrays in Pascal.
C is inherently unsafe, in part because indexes out of bounds are not systematically caught at run-time. C# is safe in this respect. An index out of bound in a running C# program raises an exception, see Chapter 36.
C programmers may be puzzled by the fact that strings are immutable in C#. Once a string is constructed, it is not possible to modify the character elements of the string. This is also a characteristic of strings in Java. This makes it possible to share a given string in several contexts. The bookkeeping behind this is called interning. (You can, for instance, read about interning in the documentation of the static method String.Intern). In case mutable strings are necessary, the class System.Text.StringBuilder makes them available.
The well-known double quote string notation is used both in C and C#. Escape characters, prefixed with backslashes (such as in "\n") are used in C as well and in C#. C# supports an alternative notation, called verbatim string constants, @"..." , in which the only escape notation is "" which stands for the double quote character itself. Inside a verbatim string constant it possible to have newline characters, and all backslashes appear as backslash characters in the string. An example of a verbatim strings will be shown in Program 6.9.
Below, in Program 6.7 we will demonstrate a number of aspects of arrays in C#.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | using System; class ArrayStringDemo{ public static void Main(){ string[] a1, a2 = {"a", "bb", "ccc"}; a1 = new string[]{"ccc", "bb", "a"}; int[,] b1 = new int[2,4], b2 = {{1,2,3,4}, {5,6,7,8}}; double[][] c1 = { new double[]{1.1, 2.2}, new double[]{3.3, 4.4, 5.5, 6.6} }; Console.WriteLine("Array lengths. a1:{0} b2:{1} c1:{2}", a1.Length, b2.Length, c1.Length); Array.Clear(a2,0,3); Array.Resize<string>(ref a2,10); Console.WriteLine("Lenght of a2: {0}", a2.Length); Console.WriteLine("Sorting a1:"); Array.Sort(a1); foreach(string str in a1) Console.WriteLine(str); } } | |||
|
We declare two variables, a1 and a2, of the type string[]. In other words, a1 and a2 are both arrays of strings. a1 is not initialized in its declaration. (Local variables in C# are not initialized to any default value). a2 is initialized by means of an array initializer, namely {"a", "bb", "ccc"}. The length of the array is determined by the number of expressions within the pair of curly braces.
The arrays b1 and b2 are both rectangular 2 times 4 arrays.
The array c1 is an example of a jagged array. c1[1] is an array of length 2. c1[2] is an array of length 4.
Next we try out the Length operation on a1, b2 and c1. The result is a1:3 b2:8 c1:2. Please notice and understand the outcome.
Finally we demonstrate a number of additional operations on arrays: Clear, Resize, and Sort. These are all methods in the class System.Array.
The output of the array demo program is shown in Listing 6.8 (only on web).
Arrays, as discussed above, will be used in many of your future programs. But as an alternative, you should be aware of the collection classes, in particular the type parameterized, "generic" collection classes. These classes will be discussed in Chapter 45.
1 2 3 4 5 6 | Array lengths. a1:3 b2:8 c1:2 Lenght of a2: 10 Sorting a1: a bb ccc | |||
|
Now we will study a program example that illustrates uses of the type string.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | using System; class ArrayStringDemo{ public static void Main(){ string s1 = "OOP"; System.String s2 = "\u004f\u004f\u0050"; // equivalent Console.WriteLine("s1 and s2: {0} {1}", s1, s2); string s3 = @"OOP on the \n semester ""Dat1/Inf1/SW3"""; Console.WriteLine("\n{0}", s3); string s4 = "OOP on \n the \\n semester \"Dat1/Inf1/SW3\""; Console.WriteLine("\n{0}", s4); string s5 = "OOP E06".Substring(0,3); Console.WriteLine("The substring is: {0}", s5); } } | |||
|
The strings s1 and s2 in Program 6.9 contain the same three characters, namely 'O', 'O', and 'P'.
Similarly, the strings referred by s3 and s4 are equal to each other (in the sense that they contain the same sequences of characters). As already mentioned above, the string constant in line 10-11 is a verbatim string constant, in which an escape sequence like \n denotes itself. In verbatim strings, only "" has a special interpretation, namely as a single quoute character.
Finally, in Program 6.9, the Substring operation from the class System.String is demonstrated.
The output of the string demonstration program in Program 6.9 is shown in Listing 6.10 (only on web).
1 2 3 4 5 6 7 8 | s1 and s2: OOP OOP OOP on the \n semester "Dat1/Inf1/SW3" OOP on the \n semester "Dat1/Inf1/SW3" The substring is: OOP | |||
|
Exercise 2.5. Use of array types Based on the inspiration from the accompanying example, you are in this exercise supposed to experiment with some simple C# arrays. First, consult the documentation of the class System.Array. Please notice the properties and methods that are available on arrays in C#. Declare, initialize, and print an array of names (e.g. array of strings) of all members of your group. Sort the array, and search for a given name using System.Array.BinarySearch method. Reverse the array, and make sure that the reversing works. Solution |
Exercise 2.6. Use of string types Based on the inspiration from the accompanying example, you are in this exercise supposed to experiment with some simple C# strings. First, consult the documentation of the class System.String - either in your documentation browser or at msdn.microsoft.com. Read the introduction (remarks) to string which contains useful information! There exists a large variety of operations on strings. Please make sure that you are aware of these. Many of them will help you a lot in the future! Make a string of your own first name, written with escaped Unicode characters (like we did for "OOP" in the accompanying example). If necessary, consult the unicode code charts (Basic Latin and Latin-1) to find the appropriate characters. Take a look at the System.String.Insert method. Use this method to insert your last name in the first name string. Make your own observations about Insert relative to the fact that strings in C# are immutable. Solution |
6.5. Pointers and references
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
Pointers are instrumental in almost all real-life C programs, both for handling dynamic memory allocation, and for dealing with arrays. Recall that an array in C is simply a pointer to the first element of the array.
References in C# (and Java) can be understood as a restricted form of pointers. C# references are never explicitly dereferenced, references are not coupled to arrays, and references cannot be operated on via the arithmetic C# operators; There are no pointer arithmetic in (the safe part of) C#. As a special notice to C++ programmers: References in C# have nothing to do with references in C++.
Here follows an itemized overview of pointers and references.
|
Program 6.11 shows some basic uses of references in C#. The variables cRef and anotherCRef are declared of type C. C happens to be an almost trivial class that we have defined in line 3-5. Classes are reference types in C (see Chapter 13). cref declared in in line 11 is assigned to null (a reference to nothing) in line 12. Next, in line 15, cref is assigned to a new instance of C. Via the reference in cref we can access the members x and y in the C object, see line 18. We can also pass a reference as a parameter to a function F as in line 19. This does not copy the referenced object when entering F.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | using System; public class C { public double x, y; } public class ReferenceDemo { public static void Main(){ C cRef, anotherCRef; cRef = null; Console.WriteLine("Is cRef null: {0}", cRef == null); cRef = new C(); Console.WriteLine("Is cRef null: {0}", cRef == null); Console.WriteLine("x and y are ({0},{1})", cRef.x, cRef.y); anotherCRef = F(cRef); } public static C F(C p){ Console.WriteLine("x and y are ({0},{1})", p.x, p.y); return p; } } | |||
|
The output of Program 6.11 is shown in Listing 6.12 (only on web).
1 2 3 4 | Is cRef null: True Is cRef null: False x and y are (0,0) x and y are (0,0) | |||
|
There is no particular complexity in normal C# programs due to use of references |
6.6. Structs
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
Structs are well-known by C programmers. It is noteworthy that arrays and structs are handled in very different ways in C. In C, arrays are deeply connected to pointers. Related to the discussion in this material, we will say that pointers are dealt with by reference semantics, see Section 13.1. Structs in C are dealt with by value semantics, see Section 14.1. Structs are copied by assignment, parameter passing, and returns. Arrays are not!
Let us now compare structs in C and C#:
|
In Program 6.13 we see a program with a struct called Point. The variable p1 contains a point (3.0, 4.0). Because structs are value types, p1 does not refer to a point. It contains the two coordinates of type double that represents the points. p2 is uninitialized. In line 15, p1 is copied into p2. This is a field-by-field (bit-by-bit) copy operation. No manipulation of references is involved. Finally we show the activation of a method Mirror on p2. Hereby the state of the second point is mutated to (-3,-4).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | using System; public struct Point { public double x, y; public Point(double x, double y){this.x = x; this.y = y;} public void Mirror(){x = -x; y = -y;} } // end Point public class StructDemo{ public static void Main(){ Point p1 = new Point(3.0, 4.0), p2; p2 = p1; p2.Mirror(); Console.WriteLine("Point is: ({0},{1})", p2.x, p2.y); } } | |||
|
6.7. Operators
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
Expressions in C# have much in common with expressions in C. Non-trivial expressions are built with use of operators. We summarize the operators in C# in Table 6.1. As it appears, there is a substantial overlap with the well-known operators i C. Below the table we will comment on the details.
Level | Category | Operators | Associativity (binary/tertiary) |
14 | Primary | x.y f(x) a[x] x++ x-- new typeof checked unchecked default delegate | left to right |
13 | Unary | + - ! ~ ++x --x (T)x true false sizeof | left to right |
12 | Multiplicative | * / % | left to right |
11 | Additive | + - | left to right |
10 | Shift | << >> | left to right |
9 | Relational and Type testing | < <= > >= is as | left to right |
8 | Equality | == != | left to right |
7 | Logical/bitwise and | & | left to right |
6 | Logical/bitwise xor | ^ | left to right |
5 | Logical/bitwise or | | | left to right |
4 | Conditional and | && | left to right |
3 | Conditional or | || | left to right |
2 | Null coalescing | ?? | left to right |
1 | Conditional | ?: | right to left |
0 | Assignment or Lambda expression | = *= /= %= += -= <<= >>= &= ^= |= => | right to left |
Table 6.1 The operator priority table of C#. Operators with high level numbers have high priorities. In a given expression, operators of high priority are evaluated before operators with lower priority. The associativity tells if operators at the same level are evaluated from left to right or from right to left. |
The operators shown in red are new and specific to C#. The operator new creates an instance (an object) of a class or it initializes a value of struct. We have already encountered new in some of the simple demo programs, for instance Program 6.11 and Program 6.13. See Section 12.2 for details on new. The operators is, as and typeof will not be discussed here. Please refer to Section 28.12 for details on these. The operations checked and uncheked relate to the safe and unsafe part of C# respectively. In this material we only deal with the safe part, and therefore these two C# operators can be disregarded. The default operator gives access to the default value of value types, see Section 12.3. The delegate operator is used for definition of anonymous functions, see Section 22.1. The unary true and false operators tell when a value in a user defined type is regarded as true or false respectively. See Section 21.2 for more details. The expression x ?? y is a convenient shortcut of x != null ? x : y. See Section 14.9 for more details on ??. => is the operator which is used to form lambda expressions in C#3.0, see Section 22.4.
A couple of C operators are not part of C#. The address operator & and the dereference operator * are not found in (the safe part of) C# (but they are actually available in the unsafe part of the language). They are both related to pointers, and as discussed in Section 6.5 pointers are not supported in (the safe part of) C#.
All the remaining operators should be familiar to the C programmer.
The operators listed above have fixed and predefined meanings when used together with primitive types in C#. On top of these it is possible to define new meanings of some of the operators on objects/values of your own types. This is called operator overloading, and it is discussed in more details in Chapter 21. The subset of overloadable operators is highlighted in Table 21.1.
6.8. Commands and Control Structures
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
Almost all control structures in C can be used the same way in C# |
Commands (also known as statements) are able to mutate the program state at run-time. As such, the most important command is the assignment. The commands constitute the "imperative heart" of the programming language. The control structures provide means for sequencing the commands.
The commands and control structures of C# form - to a large extent - a superset of the commands and control structures of C. Thus, the knowledge of commands and control structures in C can be used directly when learning C#.
As usual, we will summarize similarities and differences between C and C#. The similarities are the following:
|
As in C, an expression becomes a command if it is followed by a semicolon. Therefore we have emphasized the semicolon above in the assignment a = a + 5;
As it will be clear from Program 6.15 below, the switch control structures in C and C# differ substantially.
The main differences between C and C# regarding control structures are the following:
|
The foreach control structures is an easy-to-use version of a for loop, intended for start-to-end traversal of collections. We will not here touch on try-catch-finally and throw. Please refer to our coverage of exception handling in Section 36.2 for a discussion of these.
Let us now look at some concrete program examples with control structures. In the examples below, program fragments shown in red color illustrate erroneous aspects. Program fragments shown in green are all right.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | /* Right, Wrong */ using System; class IfDemo { public static void Main(){ int i = 0; /* if (i){ Console.WriteLine("i is regarded as true"); } else { Console.WriteLine("i is regarded as false"); } */ if (i != 0){ Console.WriteLine("i is not 0"); } else { Console.WriteLine("i is 0"); } } } | |||
|
The if-else control structure has survived from C. Program 6.14 in reality illustrates a difference between handling of boolean values of C and C#. This has already been treated in Section 6.1. The point is that an expression of non-bool type (such the integer i) cannot be used as the controlling expression of an if-else control structure i C#.
Let us now look at a program with switch control structures. As already mentioned earlier there are a number of noteworthy differences between C and C# regarding switch.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | /* Right, Wrong */ using System; class SwitchDemo { public static void Main(){ int j = 1, k = 1; /* switch (j) { case 0: Console.WriteLine("j is 0"); case 1: Console.WriteLine("j is 1"); case 2: Console.WriteLine("j is 2"); default: Console.WriteLine("j is not 0, 1 or 2"); } */ switch (k) { case 0: Console.WriteLine("m is 0"); break; case 1: Console.WriteLine("m is 1"); break; case 2: Console.WriteLine("m is 2"); break; default: Console.WriteLine("m is not 0, 1 or 2"); break; } switch (k) { case 0: case 1: Console.WriteLine("n is 0 or 1"); break; case 2: case 3: Console.WriteLine("n is 2 or 3"); break; case 4: case 5: Console.WriteLine("n is 4 or 5"); break; default: Console.WriteLine("n is not 1, 2, 3, 4, or 5"); break; } string str = "two"; switch (str) { case "zero": Console.WriteLine("str is 0"); break; case "one": Console.WriteLine("str is 1"); break; case "two": Console.WriteLine("str is 2"); break; default: Console.WriteLine("str is not 0, 1 or 2"); break; } } } | |||
|
The first switch in Program 6.15 is legal in C, but it is illegal i C#. It illustrates the fall through problem. If j is 0, case 0, 1, 2, and default are all executed in a C program. Most likely, the programmer intended to write the second switch, starting in line 17, in which each case is broken with use of the break command. In C# the compiler checks that each branch of a switch never encounters the ending of the branch (and, thus, never falls through to the succeeding branch).
The third switch in the demo program shows that two or more cases can be programmed together. Thus, like in C, it is legal to fall trough empty cases.
The final switch shows that it is possible to switch on strings in C#. This is very convenient in many contexts! In C, the type of the switch expression must be integral (which means an integer, char, or an enumeration type).
Let us also mention that C# allows special goto constructs (goto case constant and goto default) inside a switch. With use of these it is possible to jump from one case to another, and it is even possible to program a loop inside a switch (by jumping from one case to an already executed case). It is recommended only to use these specialized goto constructs in exceptional situations, or for programming of particular patterns (in which it is natural to organize a solution around multiple branches that can pass the control to each other).
Next we will study a program that illustrates the foreach loop.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /* Right, Wrong */ using System; class ForeachDemo { public static void Main(){ int[] ia = {1, 2, 3, 4, 5}; int sum = 0; foreach(int i in ia) sum += i; Console.WriteLine(sum); } } | |||
|
As mentioned above, foreach is a variant of a for loop that traverses all elements of a collection. (See how this is provided for in Section 31.6). In the example of Program 6.16 all elements of the array are traversed. Thus, the loop variable i will be 1, 2, 3, 4 and 5 in succession. Many efforts in C# have been directed towards supporting foreach on the collection types that you program yourself. Also notice that loop control variable, i, is declared inside the foreach construct. This cannot be done in a conventional for loop in C (although it can be done in C99 and in C++).
Finally, we will see an example of try-catch.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | /* Right, Wrong */ using System; class TryCatchDemo { public static void Main(){ int i = 5, r = 0, j = 0; /* r = i / 0; Console.WriteLine("r is {0}", r); */ try { r = i / j; Console.WriteLine("r is {0}", r); } catch(DivideByZeroException e){ Console.WriteLine("r could not be computed"); } } } | |||
|
Division by 0 is a well-known cause of a run-time error. Some compilers are, in some situations, even smart enough to identify the error at compile-time. In Program 6.17 the erroneous program fragment never reaches the activation of WriteLine in line 10, because the division by zero halts the program.
The expression i / j, where j is 0, is embedded in a try-catch control structure. The division by zero raises an exception in the running program, which may be handled in the catch part. The WriteLine in line 17 is encountered in this part of the example. Thus, the program survives the division by zero. Later in the material, starting in Chapter 33, we discuss - in great details - errors and error handling and the use of try-catch.
Before we leave the assignments and control structure we want to mention the definite assignment rule in C#. The rule states that every declared variable must be assigned to a value before the variable is used. The compiler enforces the rule. Take a look at the program below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | using System; class DefiniteAssignmentDemo{ public static void Main(){ int a, b; bool c; if (ReadFromConsole("Some Number") < 10){ a = 1; b = 2; } else { a = 2; } Console.WriteLine(a); Console.WriteLine(b); // Use of unassigned local variable 'b' while (a < b){ c = (a > b); a = Math.Max(a, b); } Console.WriteLine(c); // Use of unassigned local variable 'c' } public static int ReadFromConsole(string prompt){ Console.WriteLine(prompt); return int.Parse(Console.ReadLine()); } } | |||
|
The program declares three variables a, b, and c in line 6-7, without initializing them. Variable b is used in line 16, but it cannot be guarantied that the if-else control structure in line 9-13 assigns a value to the variable b. Therefore, using a conservative approach, the compiler complains about line 16. The error message is emphasized in the comment at the end of line 16.
Similarly, the variable c declared in line 7 is not necessarily assigned by the while control structure in line 18-21. Recall that if a >= b when we enter the while loop, the line 19 and 20 are never executed. This explains the error message associated to line 24.
The definite assignment rule, enforced by the compiler, implies that we never get run-time errors due to uninitialized variables. On the other hand, the rule also prevents some program from executing on selected input. If the number read in line 9 of Program 6.18 is less than 10 both b and c will be assigned when used in the WriteLine calls.
6.9. Functions
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
Functions are the primary abstractions in C. In C# "function" (or "function member") is the common name of a variety of different kinds of abstractions. The most well-known of these is known as methods. The others are properties, events, indexers, operators, and constructors.
Functions in C# belong to types: classes or structs. Thus, functions in C# cannot be freestanding like in C. Functions are always found inside a type.
The conceptual similarities between functions in C and methods in C# are many and fundamental. In our context it is, however, most interesting to concentrate on the differences:
|
In C all parameters are passed by value. However, passing a pointer by value in C is often proclaimed as call by reference. In C# there are several parameter passing modes: call by value and two variants of call by reference (ref and out parameters). The default parameter passing mode is call by value. Call by reference parameter passing in C (via pointers) is not the same as ref parameters in C#. ref parameters in C# are much more like Pascal var (variable) parameters.
In C it is possible, but messy, to deal with functions of a variable (or an arbitrary) number of arguments. In C# this is easier and cleaner. It is supported by the params keyword in a formal parameter list. An example is provided in Program 6.20.
A function in C is identified by its name. A method in C# is identified by its name together with the types of the formal parameters (the so-called method signature). This allows several methods with the same names to coexist, provided that their formal parameter types differ. A set of equally named methods (with different formal parameter types) is known as overloaded methods.
A function in C# can be handled without naming it at all. Such functions are known as delegates. Delegates come from the functional programming language paradigm, where functions most often are first class objects. Something of first class can be passed as parameters, returned as results from functions, and organized in data structures independent of naming. Delegates seem to be more and more important in the development of C#. In C# 3.0 the nearby concepts of lambda expressions and expression trees have emerged. We have much more to say about delegates later in this material, see Chapter 22.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | /* Right, Wrong */ using System; /* public int Increment(int i){ return i + 1; } public void Main (){ int i = 5, j = Increment(i); Console.WriteLine("i and j: {0}, {1}", i, j); } // end Main */ public class FunctionDemo { public static void Main (){ SimpleFunction(); } public static void SimpleFunction(){ int i = 5, j = Increment(i); Console.WriteLine("i and j: {0}, {1}", i, j); } public static int Increment(int i){ return i + 1; } } | |||
|
Program 6.19 shows elementary examples of functions (methods) in a C# program. The program text decorated with red color shows two functions, Main and Increment, outside of any type. This is illegal in C#.
Shown in green we again see the function Increment, now located in a legal context, namely inside the type (class) FunctionDemo. The function SimpleFunction calls Increment in a straightforward way. The function Main serves as main program in C#. It is here the program starts. We see that Main calls SimpleFunction.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | using System; public class FunctionDemo { public static void Main (){ ParameterPassing(); } public static void ValueFunction(double d){ d++;} public static void RefFunction(ref double d){ d++;} public static void OutFunction(out double d){ d = 8.0;} public static void ParamsFunction(out double res, params double[] input){ res = 0; foreach(double d in input) res += d; } public static void ParameterPassing(){ double myVar1 = 5.0; ValueFunction(myVar1); Console.WriteLine("myVar1: {0:f}", myVar1); // 5.00 double myVar2 = 6.0; RefFunction(ref myVar2); Console.WriteLine("myVar2: {0:f}", myVar2); // 7.00 double myVar3; OutFunction(out myVar3); Console.WriteLine("myVar3: {0:f}", myVar3); // 8.00 double myVar4; ParamsFunction(out myVar4, 1.1, 2.2, 3.3, 4.4, 5.5); // 16.50 Console.WriteLine("Sum in myVar4: {0:f}", myVar4); } } | |||
|
The four functions in Program 6.20, ValueFunction, RefFunction, OutFunction, and ParamsFunction demonstrate the different parameter passing techniques of C#.
The call-by-value parameter d in ValueFunction has the same status as a local variable in ValueFunction. Therefore, the call of ValueFunction with myVar1 as actual parameter does not affect the value of myVar1. It does, however, affect the value of d in ValueFunction, but this has no lasting effect outside ValueFunction. In a nutshell, this is the idea of call by value parameters.
In RefFunction, the formal parameter d, is a ref parameter. The corresponding actual parameter must be a variable. And indeed it is a variable in our sample activation of RefFunction, namely the variable named myVar2. Inside RefFunction, the formal parameter d is an alias of the actual parameter (myVar2). Thus, the incrementing of d actually increments myVar2. Pascal programmers will be familiar with this mechanism (via var parameters) but C programmers have not encountered this before - at least not while programming in C.
OutFunction demonstrates the use of an out parameter. out parameters are similar to ref parameters, but only intended for data output from the function. More details of ref and out parameters appears in Section 20.6 and Section 20.7.
Notice that in C#, the keywords ref and out must be used both in the formal parameter list and in the actual parameter list. This is nice, because you will hereby spot the parameter passing mode in calls. In most other programming language it is necessary to consult the function definition to find out about the parameter passing modes of the parameters involved.
The first parameter of ParamsFunction, res, is an out parameter, intended for passing the sum of the input parameter back to the caller. The formal param parameter, input, must be an array. The similar actual parameters (occurring at the end of the actual parameter list) are inserted as elements into a new array, and bound to the formal parameter input. With this mechanism, an arbitrary number of "rest parameters" (of the same or comparable types) can be handled, and bundled into an array in the C# function, which is being called.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | using System; public class FunctionDemo { public static void Main (){ Overloading(); } public static void F(int p){ Console.WriteLine("This is F(int) on {0}", p); } public static void F(double p){ Console.WriteLine("This is F(double) on {0}", p); } public static void F(double p, bool q){ Console.WriteLine("This is F(double,bool) on {0}, {1}", p, q); } public static void F(ref int p){ Console.WriteLine("This is F(ref int) on {0}", p); } public static void Overloading(){ int i = 7; F(i); // This is F(int) on 7 F(5.0); // This is F(double) on 5 F(5.0, false); // This is F(double,bool) on 5, False F(ref i); // This is F(ref int) on 7 } } | |||
|
Program 6.21 (only on web) shows a class with four methods, all of which are named F. These functions are distinguished by different formal parameters, and by different parameter passing modes. Passing an integer value parameter activates the first F. Passing a double value parameter activates the second F. Passing a double and a bool (both as values) activates the third F. Finally, passing an integer ref parameter activates the fourth F.
6.10. Input and output
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
In C, the functions printf and scanf are important for handling output to the screen, input from the keyboard, and file IO as well. It is therefore interesting for C programmers to find out how the similar facilities work in C#.
In C#, the Console class encapsulates the streams known as standard input and standard output, which per default are connected to the keyboard and the screen. The various write functions in the Console class are quite similar to the printf function in C. The Console class' read functions are not as advanced as scanf in C. There is not direct counterpart of the C scanf function in C#.
First, in Program 6.22 we will study uses of the Write and WriteLine functions.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | /* Right, Wrong */ using System; public class OutputDemo { // Placeholder syntax: {<argument#>[,<width>][:<format>[<precision>]]} public static void Main(){ Console.Write( "Argument number only: {0} {1}\n", 1, 1.1); // Console.WriteLine("Formatting code d: {0:d},{1:d}", 2, 2.2); Console.WriteLine("Formatting codes d and f: {0:d} {1:f}", 3, 3.3); Console.WriteLine("Field width: {0,10:d} {1,10:f}", 4, 4.4); Console.WriteLine("Left aligned: {0,-10:d} {1,-10:f}", 5, 5.5); Console.WriteLine("Precision: {0,10:d5} {1,10:f5}", 6, 6.6); Console.WriteLine("Exponential: {0,10:e5} {1,10:e5}", 7, 7.7); Console.WriteLine("Currency: {0,10:c2} {1,10:c2}", 8, 8.887); Console.WriteLine("General: {0:g} {1:g}", 9, 9.9); Console.WriteLine("Hexadecimal: {0:x5}", 12); Console.WriteLine("DateTime formatting with F: {0:F}", DateTime.Now); Console.WriteLine("DateTime formatting with G: {0:G}", DateTime.Now); Console.WriteLine("DateTime formatting with T: {0:T}", DateTime.Now); } } | |||
|
Like printf in C, the methods Write and WriteLine accept a control string and a number of additional parameters which are formatted and inserted into the control string. Write and WriteLine actually rely on an underlying Format method in class String. Notice that a there exists many overloaded Write and WriteLine methods in the class Console. Here we concentrate of those that take a string - the control string - as the first parameter.
The following call of printf in C
printf("x: %d, y: %5.2f, z: %Le\n", x, y, z);
is roughly equivalent to the following call of of WriteLine in C#
Console.WriteLine("x: {0:d}, y: {1,5:F2}, z: {2:E}", x, y, z);
The equivalence assumes that x is of type int, y is a float, and that z is a long double.
The general syntax of a placeholder (the stuff in between pairs of curly braces) in a C# formatting string is
{<argument#>[,<width>][:<format>[<precision>]]}
where [...] denotes optionality (zero or one occurrence).
C programmers do often experience strange and erroneous formatting of output if the conversion characters (such as d, f, and e in the example above) are inconsistent with the actual type of the corresponding variables or expressions (x, y, and z in the example). In C#, such problems are caught by the compiler, and as such they do not lead to wrong results. This is a much needed improvement.
Let us briefly explain the examples in line 9-23 of Program 6.22. In line 9 the default formatting is used. This corresponds to the letter code g. In line 10 an error occurs because the code d only accepts integers. The number 2.2 is not an integer. In line 13 we illustrate use of width 10 for an integer and a floating-point number. Line 14 is similar, but it uses left justification (because the width is negative). Line 15 illustrates use of the precision 5 for an integer and a floating-point number. In line 16 we format two numbers in exponential (scientific) notation. In line 17 we illustrate formatting of currencies (kroner or dollars, for instance, dependent on the culture setting). Line 18 corresponds to line 9. Line 19 calls for hexadecimal formatting of a number.
One way to learn more about output formatting is to consult the documentation of the static method Format in class System.String. From there, goto Formatting Overview. Later in this material, in Section 31.7, we will see how we can program custom formatting of our own types.
The last three example lines in Program 6.22 illustrate formatting of objects of type DateTime in C#. Such objects represent at point in time. In the example, the expression DateTime.Now denotes the current point in time.
The output of Program 6.22 is shown in Listing 6.23 (only on web).
1 2 3 4 5 6 7 8 9 10 11 12 | Argument number only: 1 1,1 Formatting codes d and f: 3 3,30 Field width: 4 4,40 Left aligned: 5 5,50 Precision: 00006 6,60000 Exponential: 7,00000e+000 7,70000e+000 Currency: kr 8,00 kr 8,89 General: 9 9,9 Hexadecimal: 0000c DateTime formatting with F: 4. juli 2008 15:35:31 DateTime formatting with G: 04-07-2008 15:35:31 DateTime formatting with T: 15:35:31 | |||
|
We now switch from output to input.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | /* Right, Wrong */ using System; public class InputDemo { public static void Main(){ Console.Write("Input a single character: "); char ch = (char)Console.Read(); Console.WriteLine("Character read: {0}", ch); Console.ReadLine(); Console.Write("Input an integer: "); int i = int.Parse(Console.ReadLine()); Console.WriteLine("Integer read: {0}", i); Console.Write("Input a double: "); double d = double.Parse(Console.ReadLine()); Console.WriteLine("Double read: {0:f}", d); } } | |||
|
In Program 6.24 Console.Read() reads a single character. The result returned is a positive integer, or -1 if no character can be read (typically because we are located at the end of an input file). Read blocks until enter is typed. Non-blocking input is also available via the method Console.ReadKey. The expression Console.ReadLine() reads a line of text into a string. The last two, highlighted examples show how to read a text string and, via the Parse method in type int and double, to convert the strings read to values of type int and double respectively. Notice that scanf in C can take hand of such cases.
The output of Program 6.24 is shown in Listing 6.25 (only on web).
1 2 3 4 5 6 | Input a single character: a Character read: a Input an integer: 123 Integer read: 123 Input a double: 456,789 Double read: 456,79 | |||
|
Later in this material we have much more to say about input and output in C#. See Chapter 37 - Chapter 39 . The most important concept, which we will deal with in these chapters, is the various kinds of streams in C#.
6.11. Comments
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
We finalize our comparison of C and C# with an overview of the different kinds of C# comments. Recall that C only supports delimited comments (although C programmers also often use single-line comments, which actually is used in C++ and in newer versions of C (C99)).
C# supports two different kinds of comments and XML variants of these:
|
XML comments can only be given before declarations, not inside other fragments. XML comments are used for documentation of types. We have much more to say about XML comments in our discussion of documentation of C# programs. Delimited C# comments cannot be nested.
6.12. References
[Decimal-floating-point] | Decimal Floating Point in .NET http://www.yoda.arachsys.com/csharp/decimal.html |