Chapter 3. Variables and Expressions
Provided by: Karli Watson
This topic contains the following sections.
- Basic C# Syntax
- Basic C# Console Application Structure
- Variables
- Expressions
- Summary
- Exercises
To use C# effectively, it’s important to understand what you’re actually doing when you create a computer program. Perhaps the most basic description of a computer program is that it is a series of operations that manipulate data. This is true even of the most complicated examples, such as vast, multifeatured Windows applications (e.g., Microsoft Office Suite). Although this is often completely hidden from users of applications, it is always going on behind the scenes.
To illustrate this further, consider the display unit of your computer. What you see onscreen is often so familiar that it is difficult to imagine it as anything other than a “moving picture.” In fact, what you see is only a representation of some data, which in its raw form is merely a stream of 0s and 1s stashed away somewhere in the computer’s memory. Any onscreen action—moving a mouse pointer, clicking on an icon, typing text into a word processor—results in the shunting around of data in memory.
Of course, simpler situations show this just as well. When using a calculator application, you are supplying data as numbers and performing operations on the numbers in much the same way as you would with paper and pencil—but a lot quicker!
If computer programs are fundamentally performing operations on data, then this implies that you need a way to store that data, and some methods to manipulate it. These two functions are provided by variables and expressions, respectively, and this chapter explores what that means, both in general and specific terms.
In this chapter, you learn about:
Basic C# syntax
Variables and how to use them
Expressions and how to use them
Before starting that, though, you’ll take a look at the basic syntax involved in C# programming, because you need a context in which you can learn about and use variables and expressions in the C# language.
Basic C# Syntax
The look and feel of C# code is similar to that of C++ and Java. This syntax can look quite confusing at first and it’s a lot less like written English than some other languages. However, as you immerse yourself in the world of C# programming, you’ll find that the style used is a sensible one, and it is possible to write very readable code without much effort.
Unlike the compilers of some other languages, C# compilers ignore additional spacing in code, whether it results from spaces, carriage returns, or tab characters (characters collectively known as white space characters). This means you have a lot of freedom in the way that you format your code, although conforming to certain rules can help make things easier to read.
C# code is made up of a series of statements, each of which is terminated with a semicolon. Because white space is ignored, multiple statements can appear on one line, although for readability it is usual to add carriage returns after semicolons, to avoid multiple statements on one line. It is perfectly acceptable (and quite normal), however, to use statements that span several lines of code.
C# is a block-structured language, meaning statements are part of a block of code. These blocks, which are delimited with curly brackets ({ and }), may contain any number of statements, or none at all. Note that the curly bracket characters do not need accompanying semicolons.
For example, a simple block of C# code could take the following form:
{
<code line 1, statement 1>;
<code line 2, statement 2>
<code line 3, statement 2>;
}
Here the <code line x, statement y> sections are not actual pieces of C# code; this text is used as a placeholder where C# statements would go. In this case, the second and third lines of code are part of the same statement, because there is no semicolon after the second line.
The following simple example uses indentation to clarify the C# itself. This is actually standard practice, and in fact VS automatically does this for you by default. In general, each block of code has its own level of indentation, meaning how far to the right it is. Blocks of code may be nested inside each other (that is, blocks may contain other blocks), in which case nested blocks will be indented further:
{
<code line 1>;
{
<code line 2>;
<code line 3>;
}
<code line 4>;
}
In addition, lines of code that are continuations of previous lines are usually indented further as well, as in the third line of code in the first example above.
Note
If you look in the VCE Options dialog (select Tools Options), you can find the rules that VCE uses for formatting your code. There are very many of these, in subcategories of the Text Editor → C# → Formatting node. Most of the settings here reflect parts of C# that haven’t been covered yet, but you might want to return to these settings later if you want to tweak them to suit your personal style better. For clarity, this book shows all code snippets as they would be formatted by the default settings.
Of course, this style is by no means mandatory. If you don’t use it, however, you will quickly find that things can get very confusing as you move through this book!
Something else you often see in C# code are comments. A comment is not, strictly speaking, C# code at all, but it happily cohabits with it. Comments are self-explanatory: They enable you to add descriptive text to your code—in plain English (or French, German, Outer Mongolian, and so on)—which is ignored by the compiler. When you start dealing with lengthy code sections, it’s useful to add reminders about exactly what you are doing, such as “this line of code asks the user for a number” or “this code section was written by Bob.” C# has two ways of doing this. You can either place markers at the beginning and end of a comment, or you can use a marker that means “everything on the rest of this line is a comment.” This latter method is an exception to the rule mentioned previously about C# compilers ignoring carriage returns, but it is a special case.
To indicate comments using the first method, you use /* characters at the start of the comment and */ characters at the end. These may occur on a single line, or on different lines, in which case all lines in between are part of the comment. The only thing you can’t type in the body of a comment is */, because this is interpreted as the end marker. For example, the following are OK:
/* This is a comment */
/* And so.
. is this! */
The following, however, cause problems:
/* Comments often end with “*/” characters */
Here the end of the comment (the characters after “*/”) will be interpreted as C# code, and errors will occur.
The other commenting approach involves starting a comment with //. After that, you can write whatever you like—as long as you keep to one line! The following is OK:
// This is a different sort of comment.
The following fails, because the second line is interpreted as C# code:
// So is this,
but this bit isn’t.
This sort of commenting is useful to document statements, because both can be placed on a single line:
<A statement>; // Explanation of statement
It was stated earlier that there are two ways of commenting C# code, but there is a third type of comment in C#—although strictly speaking this is an extension of the // syntax. You can use single-line comments that start with three / symbols instead of two, like this:
/// A special comment
Under normal circumstances, they are ignored by the compiler—just like other comments, but you can configure VS to extract the text after these comments and create a specially formatted text file when a project is compiled, which you can then use to create documentation. This is covered in detail in Chapter 31.
A very important point to note about C# code is that it is case sensitive. Unlike some other languages, you must enter code using exactly the right case, because using an uppercase letter instead of a lowercase one will prevent a project from compiling. For example, consider the following line of code, taken from Chapter 2:
Console.WriteLine(“The first app in Beginning C# Programming!”);
This code is understood by the C# compiler, as the case of the Console.WriteLine() command is correct. However, none of the following lines of code work:
console.WriteLine(“The first app in Beginning C# Programming!”);
CONSOLE.WRITELINE(“The first app in Beginning C# Programming!”);
Console.Writeline(“The first app in Beginning C# Programming!”);
Here the case used is wrong, so the C# compiler won’t know what you want. Luckily, as you will soon discover, VCE is very helpful when it comes to entering code, and most of the time it knows (as much as a program can know) what you are trying to do. As you type, it suggests commands that you might like to use, and it tries to correct case problems.
Basic C# Console Application Structure
Let’s take a closer look at the console application example from Chapter 2 (ConsoleApplication1), and break down the structure a bit. Here’s the code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
// Output text to the screen.
Console.WriteLine(“The first app in Beginning C# Programming!”);
Console.ReadKey();
}
}
}
You can immediately see that all the syntactic elements discussed in the previous section are present here—semicolons, curly braces, and comments, along with appropriate indentation.
The most important section of code at the moment is the following:
static void Main(string[] args)
{
// Output text to the screen.
Console.WriteLine(“The first app in Beginning C# Programming!”);
Console.ReadKey();
}
This is the code that is executed when you run your console application. Well, to be more precise, the code block enclosed in curly braces is executed. The comment line doesn’t do anything, as mentioned earlier; it’s just there for clarity. The other two code lines output some text to the console window and wait for a response, respectively, though the exact mechanisms of this don’t concern us for now.
Note how to achieve the code outlining functionality shown in the previous chapter, albeit for a Windows application, since it is such a useful feature. You can do this with the #region and #endregion keywords, which define the start and end of a region of code that can be expanded and collapsed. For example, you could modify the generated code for ConsoleApplication1 as follows:
#region Using directives
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
#endregion
This enables you to collapse this code into a single line and expand it again later should you want to look at the details. The using statements contained here, and the namespace statement just underneath, are explained at the end of this chapter.
Note
Any keyword that starts with a # is actually a preprocessor directive and not, strictly speaking, a C# keyword. Other than the two described here, #region and #endregion, these can be quite complicated, and they have very specialized uses. This is one subject you might like to investigate yourself after you have worked through this book.
For now, don’t worry about the other code in the example, because the purpose of these first few chapters is to explain basic C# syntax, so the exact method of how the application execution gets to the point where Console.WriteLine() is called is of no concern. Later, the significance of this additional code is made clear.
Variables
As mentioned earlier, variables are concerned with the storage of data. Essentially, you can think of variables in computer memory as boxes sitting on a shelf. You can put things in boxes and take them out again, or you can just look inside a box to see if anything is there. The same goes for variables; you place data in them and can take it out or look at it, as required.
Although all data in a computer is effectively the same thing (a series of 0s and 1s), variables come in different flavors, known as types. Using the box analogy again, boxes come in different shapes and sizes, so some items fit only in certain boxes. The reasoning behind this type system is that different types of data may require different methods of manipulation, and by restricting variables to individual types you can avoid mixing them up. For example, it wouldn’t make much sense to treat the series of 0s and 1s that make up a digital picture as an audio file.
To use variables, you have to declare them. This means that you have to assign them a name and a type. Once you have declared variables, you can use them as storage units for the type of data that you declared them to hold.
C# syntax for declaring variables merely specifies the type and variable name:
<type> <name>;
If you try to use a variable that hasn’t been declared, your code won’t compile, but in this case the compiler tells you exactly what the problem is, so this isn’t really a disastrous error. Trying to use a variable without assigning it a value also causes an error, but, again, the compiler detects this.
There is an almost infinite number of types that you can use. This is because you can define your own types to hold whatever convoluted data you like. Having said this, though, there are certain types of data that just about everyone will need to use at some point or another, such as a variable that stores a number. Therefore, you should be aware of several simple, predefined types.
Simple Types
Simple types include types such as numbers and Boolean (true or false) values that make up the fundamental building blocks for your applications. Unlike complex types, simple types cannot have children or attributes. Most of the simple types available are numeric, which at first glance seems a bit strange—surely, you only need one type to store a number?
The reason for the plethora of numeric types is because of the mechanics of storing numbers as a series of 0s and 1s in the memory of a computer. For integer values, you simply take a number of bits (individual digits that can be 0 or 1) and represent your number in binary format. A variable storing N bits allows you to represent any number between 0 and (2N– 1). Any numbers above this value are too big to fit into this variable.
For example, suppose you have a variable that can store 2 bits. The mapping between integers and the bits representing those integers is therefore as follows:
0 = 00
1 = 01
2 = 10
3 = 11
If you want to be able to store more numbers, then you need more bits (3 bits enable you to store the numbers from 0 to 7, for example).
The inevitable result of this system is that you would need an infinite number of bits to be able to store every imaginable number, which isn’t going to fit in your trusty PC. Even if there were a quantity of bits you could use for every number, it surely wouldn’t be efficient to use all these bits for a variable that, for example, was required to store only the numbers between 0 and 10 (because storage would be wasted). Four bits would do the job fine here, enabling you to store many more values in this range in the same space of memory.
Instead, a number of different integer types can be used to store various ranges of numbers, which take up differing amounts of memory (up to 64 bits). These types are shown in the following table.
Note
Each of these types uses one of the standard types defined in the .NET Framework. As discussed in Chapter 1, this use of standard types is what allows language interoperability. The names you use for these types in C# are aliases for the types defined in the framework. The table lists the names of these types as they are referred to in the .NET Framework library.
Type |
Alias For |
Allowed Values |
sbyte |
System.SByte |
Integer between –128 and 127 |
byte |
System.Byte |
Integer between 0 and 255 |
short |
System.Int16 |
Integer between –32768 and 32767 |
ushort |
System.UInt16 |
Integer between 0 and 65535 |
int |
System.Int32 |
Integer between –2147483648 and 2147483647 |
uint |
System.UInt32 |
Integer between 0 and 4294967295 |
long |
System.Int64 |
Integer between –9223372036854775808 and 9223372036854775807 |
ulong |
System.UInt64 |
Integer between 0 and 18446744073709551615 |
The u characters before some variable names are shorthand for unsigned, meaning that you can’t store negative numbers in variables of those types, as shown in the Allowed Values column of the table.
Of course, you also need to store floating-point values, those that aren’t whole numbers. You can use three floating-point variable types: float, double, and decimal. The first two store floating points in the form +/–m × 2e, where the allowed values for m and e differ for each type. decimal uses the alternative form +/–m × 10e. These three types are shown in the following table, along with their allowed values of m and e, and these limits in real numeric terms:
Type |
Alias For |
Min M |
Max M |
Min E |
Max E |
Approx. Min Value |
Approx. Max Value |
float |
System.Single |
0 |
224 |
2149 |
104 |
1.5 ∝ 10–45 |
3.4 ∝1038 |
double |
System.Double |
0 |
253 |
21075 |
970 |
5.0 ∝10–324 |
1.7 ∝10308 |
decimal |
System.Decimal |
0 |
296 |
226 |
0 |
1.0 ∝10–28 |
7.9 ∝1028 |
In addition to numeric types, three other simple types are available:
Type |
Alias For |
Allowed Values |
char |
System.Char |
Single Unicode character, stored as an integer between 0 and 65535 |
bool |
System.Boolean |
Boolean value, true or false |
string |
System.String |
A sequence of characters |
Note that there is no upper limit on the amount of characters making up a string, because it can use varying amounts of memory.
The Boolean type bool is one of the most commonly used variable types in C#, and indeed similar types are equally prolific in code in other languages. Having a variable that can be either true or false has important ramifications when it comes to the flow of logic in an application. As a simple example, consider how many questions can be answered with true or false (or yes and no). Performing comparisons between variable values or validating input are just two of the programmatic uses of Boolean variables that you will examine very soon.
Now that you’ve seen these types, consider a short example that declares and uses them. In the following Try It Out you use some simple code that declares two variables, assigns them values, and then outputs these values.
Create a new console application called Ch03Ex01 and save it in the directory C:\BegVCSharp\Chapter03.
Add the following code to Program.cs:
static void Main(string[] args) { int myInteger; string myString; myInteger = 17; myString = “\”myInteger\” is”; Console.WriteLine(“{0} {1}.”, myString, myInteger); Console.ReadKey(); }
Execute the code. The result is shown in Figure 3–1.
Figure 3–1
Note
The added code does three things:
It declares two variables.
It assigns values to those two variables.
It outputs the values of the two variables to the console.
Variable declaration occurs in the following code:
int myInteger;
string myString;
The first line declares a variable of type int with a name of myInteger, and the second line declares a variable of type string called myString.
Note
Variable naming is restricted; you can’t use just any sequence of characters. You learn about this in the section on naming variables.
The next two lines of code assign values:
myInteger = 17;
myString = “\”myInteger\” is”;
Here you assign two fixed values (known as literal values in code) to your variables using the = assignment operator (the “Expressions” section of this chapter has more details about operators). You assign the integer value 17 to myInteger, and the string “myInteger” (including the quotes) to myString. When you assign string literal values in this way, double quotation marks are required to enclose the string. Therefore, certain characters might cause problems if they are included in the string itself, such as the double quotation characters, and you must escape some characters by substituting a sequence of characters (an escape sequence) that represents the character(s) you want to use. In this example, you use the sequence \” to escape a double quotation mark:
myString = “\”myInteger\” is”;
If you didn’t use these escape sequences and tried coding this as
myString = ““myInteger” is”;
you would get a compiler error.
Note that assigning string literals is another situation in which you must be careful with line breaks—the C# compiler rejects string literals that span more than one line. If you want to add a line break, then use the escape sequence for a carriage return in your string, which is \n. For example, the assignment
myString = “This string has a\nline break.”;
would be displayed on two lines in the console view as follows:
This string has a
line break.
All escape sequences consist of the backslash symbol followed by one of a small set of characters (you’ll see the full set later). Because this symbol is used for this purpose, there is also an escape sequence for the backslash symbol itself, which is simply two consecutive backslashes (\\).
Getting back to the code, there is one more new line to look at:
Console.WriteLine(“{0} {1}.”, myString, myInteger);
This looks similar to the simple method of writing text to the console that you saw in the first example, but now you are specifying your variables. To avoid getting ahead of ourselves here, we’ll avoid a lot of the details about this line of code at this point. Suffice to say that it is the technique you will be using in the first part of this book to output text to the console window. Within the brackets you have two things:
A string
A list of variables whose values you want to insert into the output string, separated by commas
The string you are outputting, “{0} {1}.”, doesn’t seem to contain much useful text. As shown earlier, however, this is not what you actually see when you run the code. This is because the string is actually a template into which you insert the contents of your variables. Each set of curly brackets in the string is a placeholder that will contain the contents of one of the variables in the list. Each placeholder (or format string) is represented as an integer enclosed in curly brackets. The integers start at 0 and are incremented by 1, and the total number of placeholders should match the number of variables specified in the comma-separated list following the string. When the text is output to the console, each placeholder is replaced by the corresponding value for each variable. In the preceding example, the {0} is replaced with the actual value of the first variable, myString, and {1} is replaced with the contents of myInteger.
This method of outputting text to the console is what you use to display output from your code in the examples that follow. Finally, the code has the line shown in the earlier example for waiting for user input before terminating:
Console.ReadKey();
Again, the code isn’t dissected now, but you will see it frequently in later examples. For now, understand that it pauses code execution until you press a key.
Variable Naming
As mentioned in the previous section, you can’t just choose any sequence of characters as a variable name. This isn’t as worrying as it might sound at first, however, because you’re still left with a very flexible naming system.
The basic variable naming rules are as follows:
The first character of a variable name must be either a letter, an underscore character (_), or the at symbol (@).
Subsequent characters may be letters, underscore characters, or numbers.
There are also certain keywords that have a specialized meaning to the C# compiler, such as the using and namespace keywords shown earlier. If you use one of these by mistake, the compiler complains, however, so don’t worry about this.
For example, the following variable names are fine:
myBigVar
VAR1
_test
These are not, however:
99BottlesOfBeer
namespace
It’s-All-Over
Remember that C# is case sensitive, so be careful not to forget the exact case used when you declare your variables. References to them made later in the program with even so much as a single letter in the wrong case prevents compilation. A further consequence of this is that you can have multiple variables whose names differ only in case. For example, the following are all separate names:
myVariable
MyVariable
MYVARIABLE
Naming Conventions
Variable names are something you will use a lot, so it’s worth spending a bit of time learning the sort of names you should use. Before you get started, though, bear in mind that this is controversial ground. Over the years, different systems have come and gone, and many developers will fight tooth and nail to justify their personal system.
Until recently the most popular system was what is known as Hungarian notation. This system involves placing a lowercase prefix on all variable names that identify the type. For example, if a variable were of type int, then you might place an i (or n) in front of it, for example iAge. Using this system, it is easy to see a variable’s type at a glance.
More modern languages, however, such as C#, make this system tricky to implement. For the types you’ve seen so far, you could probably come up with one- or two-letter prefixes signifying each type. However, because you can create your own types, and there are many hundreds of these more complex types in the basic .NET Framework, this quickly becomes unworkable. With several people working on a project, it would be easy for different people to come up with different and confusing prefixes, with potentially disastrous consequences.
Developers have realized that it is far better to name variables appropriately for their purpose. If any doubt arises, it is easy enough to work out what the type of a variable is. In VS and VCE, you just have to hover the mouse pointer over a variable name and a pop-up box tells you what the type is soon enough.
Currently, two naming conventions are used in the .NET Framework namespaces: PascalCase and camelCase. The case used in the names indicates their usage. They both apply to names that are made up of multiple words and they both specify that each word in a name should be in lowercase except for its first letter, which should be uppercase. For camelCase terms, there is an additional rule: The first word should start with a lowercase letter.
The following are camelCase variable names:
age
firstName
timeOfDeath
These are PascalCase:
Age
LastName
WinterOfDiscontent
For your simple variables, stick to camelCase. Use PascalCase for certain more advanced naming, which is the Microsoft recommendation. Finally, note that many past naming systems involved frequent use of the underscore character, usually as a separator between words in variable names, such as yet_another_variable. This usage is now discouraged (which is just as well; it looks ugly!).
Literal Values
The previous Try It Out showed two examples of literal values: integer and string. The other variable types also have associated literal values, as shown in the following table. Many of these involve suffixes, whereby you add a sequence of characters to the end of the literal value to specify the type desired. Some literals have multiple types, determined at compile time by the compiler based on their context (also shown in the table).
Type(s) |
Category |
Suffix |
Example/Allowed Values |
bool |
Boolean |
None |
true or false |
int, uint, long, ulong |
Integer |
None |
100 |
uint, ulong |
Integer |
u or U |
100U |
long, ulong |
Integer |
l or L |
100L |
ulong |
Integer |
ul, uL, Ul, UL, lu, lU, Lu, or LU |
100UL |
float |
Real |
f or F |
1.5F |
double |
Real |
None, d, or D |
1.5 |
decimal |
Real |
m or M |
1.5M |
Char |
Character |
None |
‘a’, or escape sequence |
String |
String |
None |
“a.a”, may include escape sequences |
String Literals
Earlier in the chapter, you saw a few of the escape sequences you can use in string literals. Here is a full table of these for reference purposes:
Escape Sequence |
Character Produced |
Unicode Value of Character |
\’ |
Single quotation mark |
0x0027 |
\” |
Double quotation mark |
0x0022 |
\\ |
Backslash |
0x005C |
\0 |
Null |
0x0000 |
\a |
Alert (causes a beep) |
0x0007 |
\b |
Backspace |
0x0008 |
\f |
Form feed |
0x000C |
\n |
New line |
0x000A |
\r |
Carriage return |
0x000D |
\t |
Horizontal tab |
0x0009 |
\v |
Vertical tab |
0x000B |
The Unicode Value column of the preceding table shows the hexadecimal values of the characters as they are found in the Unicode character set. As well as the preceding, you can specify any Unicode character using a Unicode escape sequence. These consist of the standard \ character followed by a u and a four-digit hexadecimal value (for example, the four digits after the x in the preceding table).
This means that the following strings are equivalent:
“Karli\’s string.”
“Karli\u0027s string.”
Obviously, you have more versatility using Unicode escape sequences.
You can also specify strings verbatim. This means that all characters contained between two double quotation marks are included in the string, including end-of-line characters and characters that would otherwise need escaping. The only exception to this is the escape sequence for the double quotation mark character, which must be specified to avoid ending the string. To do this, place the @ character before the string:
@”Verbatim string literal.”
This string could just as easily be specified in the normal way, but the following requires this method:
@”A short list:
item 1
item 2”
Verbatim strings are particularly useful in filenames, as these use plenty of backslash characters. Using normal strings, you’d have to use double backslashes all the way along the string:
“C:\\Temp\\MyDir\\MyFile.doc”
With verbatim string literals you can make this more readable. The following verbatim string is equivalent to the preceding one:
@”C:\Temp\MyDir\MyFile.doc”
Note
As shown later in the book, strings are reference types, unlike the other types you’ve seen in this chapter, which are value types. One consequence of this is that strings can also be assigned the value null, which means that the string variable doesn’t reference a string (or anything, for that matter).
Variable Declaration and Assignment
To recap, recall that you declare variables simply using their type and name:
int age;
You then assign values to variables using the = assignment operator:
age = 25;
Note
Remember that variables must be initialized before you use them. The preceding assignment could be used as an initialization.
There are a couple of other things you can do here that you are likely to see in C# code. One, you can declare multiple variables of the same type at the same time by separating their names with commas after the type, as shown here:
int xSize, ySize;
Here, xSize and ySize are both declared as integer types.
The second technique you are likely to see is assigning values to variables when you declare them, which basically means combining two lines of code:
int age = 25;
You can use both these techniques together:
int xSize = 4, ySize = 5;
Here, both xSize and ySize are assigned different values. Note that
int xSize, ySize = 5;
results in only ySize being initialized—xSize is just declared, and it still needs to be initialized before it’s used.
Expressions
Now that you’ve learned how to declare and initialize variables, it’s time to look at manipulating them. C# contains a number of operators for this purpose, including the = assignment operator you’ve used already. By combining operators with variables and literal values (together referred to as operands when used with operators), you can create expressions, which are the basic building blocks of computation.
The operators available range from the simple to the highly complex, some of which you might never encounter outside of mathematical applications. The simple ones include all the basic mathematical operations, such as the + operator to add two operands; the complex ones include manipulations of variable content via the binary representation of this content. There are also logical operators specifically for dealing with Boolean values, and assignment operators such as =.
This chapter focuses on the mathematical and assignment operators, leaving the logical ones for the next chapter, where you examine Boolean logic in the context of controlling program flow.
Operators can be roughly classified into three categories:
Unary: Act on single operands
Binary: Act on two operands
Ternary: Act on three operands
Most operators fall into the binary category, with a few unary ones, and a single ternary one called the conditional operator (the conditional operator is a logical one and is discussed in Chapter 4). Let’s start by looking at the mathematical operators, which span both the unary and binary categories.
Mathematical Operators
There are five simple mathematical operators, two of which (+ and -) have both binary and unary forms. The following table lists each of these operators, along with a short example of its use and the result when it’s used with simple numeric types (integer and floating point).
Operator |
Category |
Example Expression |
Result |
+ |
Binary |
var1 = var2 + var3; |
var1 is assigned the value that is the sum of var2 and var3. |
- |
Binary |
var1 = var2—var3; |
var1 is assigned the value that is the value of var3 subtracted from the value of var2. |
* |
Binary |
var1 = var2 * var3; |
var1 is assigned the value that is the product of var2 and var3. |
/ |
Binary |
var1 = var2 / var3; |
var1 is assigned the value that is the result of dividing var2 by var3. |
% |
Binary |
var1 = var2 % var3; |
var1 is assigned the value that is the remainder when var2 is divided by var3. |
+ |
Unary |
var1 = +var2; |
var1 is assigned the value of var2. |
- |
Unary |
var1 = -var2; |
var1 is assigned the value of var2 multiplied by –1. |
Note
The + (unary) operator is slightly odd, as it has no effect on the result. It doesn’t force values to be positive, as you might assume—if var2 is -1, then +var is also -1. However, it is a universally recognized operator, and as such is included. The most useful fact about this operator is shown later in this book when you look at operator overloading.
The examples use simple numeric types because the result can be unclear when using the other simple types. What would you expect if you added two Boolean values together, for example? In this case, nothing, because the compiler complains if you try to use + (or any of the other mathematical operators) with bool variables. Adding char variables is also slightly confusing. Remember that char variables are actually stored as numbers, so adding two char variables together also results in a number (of type int, to be precise). This is an example of implicit conversion, which you’ll learn a lot more about shortly (and explicit conversion), because it also applies to cases where var1, var2, and var3 are of mixed types.
The binary + operator does make sense when used with string type variables. In this case, the table entry should read as shown in the following table:
Operator |
Category |
Example Expression |
Result |
+ |
Binary |
var1 = var2 + var3; |
var1 is assigned the value that is the concatenation of the two strings stored in var2 and var3. |
None of the other mathematical operators, however, work with strings.
The other two operators you should look at here are the increment and decrement operators, both of which are unary operators that can be used in two ways: either immediately before or immediately after the operand. The results obtained in simple expressions are shown in the next table:
Operator |
Category |
Example Expression |
Result |
++ |
Unary |
var1 = ++var2; |
var1 is assigned the value of var2 + 1. var2 is incremented by 1. |
–– |
Unary |
var1 =––var2; |
var1 is assigned the value of var2—1. var2 is decremented by 1. |
++ |
Unary |
var1 = var2++; |
var1 is assigned the value of var2. var2 is incremented by 1. |
— |
Unary |
var1 = var2––; |
var1 is assigned the value of var2. var2 is decremented by 1. |
These operators always result in a change to the value stored in their operand:
++ always results in its operand being incremented by one.
––always results in its operand being decremented by one.
The differences between the results stored in var1 are a consequence of the fact that the placement of the operator determines when it takes effect. Placing one of these operators before its operand means that the operand is affected before any other computation takes place. Placing it after the operand means that the operand is affected after all other computation of the expression is completed.
This merits another example! Consider this code:
int var1, var2 = 5, var3 = 6;
var1 = var2++ *—var3;
What value will be assigned to var1? Before the expression is evaluated, the—operator preceding var3 takes effect, changing its value from 6 to 5. You can ignore the ++ operator that follows var2, as it won’t take effect until after the calculation is completed, so var1 will be the product of 5 and 5, or 25.
These simple unary operators come in very handy in a surprising number of situations. They are really just a shorthand for expressions such as this:
var1 = var1 + 1;
This sort of expression has many uses, particularly where looping is concerned, as shown in the next chapter. The following Try It Out provides an example of how to use the mathematical operators, and it introduces a couple of other useful concepts as well. The code prompts you to type in a string and two numbers and then demonstrates the results of performing some calculations.
Create a new console application called Ch03Ex02 and save it to the directory C:\BegVCSharp\Chapter03.
Add the following code to Program.cs:
static void Main(string[] args) { double firstNumber, secondNumber; string userName; Console.WriteLine(“Enter your name:”); userName = Console.ReadLine(); Console.WriteLine(“Welcome {0}!”, userName); Console.WriteLine(“Now give me a number:”); firstNumber = Convert.ToDouble(Console.ReadLine()); Console.WriteLine(“Now give me another number:”); secondNumber = Convert.ToDouble(Console.ReadLine()); Console.WriteLine(“The sum of {0} and {1} is {2}.”, firstNumber, secondNumber, firstNumber + secondNumber); Console.WriteLine(“The result of subtracting {0} from {1} is {2}.”, secondNumber, firstNumber, firstNumber—secondNumber); Console.WriteLine(“The product of {0} and {1} is {2}.”, firstNumber, secondNumber, firstNumber * secondNumber); Console.WriteLine(“The result of dividing {0} by {1} is {2}.”, firstNumber, secondNumber, firstNumber / secondNumber); Console.WriteLine(“The remainder after dividing {0} by {1} is {2}.”, firstNumber, secondNumber, firstNumber % secondNumber); Console.ReadKey(); }
Execute the code. The display shown in Figure 3–2 appears.
Figure 3–2
- Enter your name and press Enter, as shown in Figure 3–3.
Figure 3–3
- Enter a number, press Enter, then another number, then Enter again, as shown in Figure 3–4.
Figure 3–4
Note
As well as demonstrating the mathematical operators, this code introduces two important concepts that you will often come across:
User input
Type conversion
User input uses a syntax similar to the Console.WriteLine() command you’ve already seen—you use Console.ReadLine(). This command prompts the user for input, which is stored in a string variable:
string userName;
Console.WriteLine(“Enter your name:”);
userName = Console.ReadLine();
Console.WriteLine(“Welcome {0}!”, userName);
This code writes the contents of the assigned variable, userName, straight to the screen.
You also read in two numbers in this example. This is slightly more involved, because the Console.ReadLine() command generates a string, but you want a number. This introduces the topic of type conversion. This is covered in more detail in Chapter 5, but let’s have a look at the code used in this example.
First, you declare the variables you want to store the number input in:
double firstNumber, secondNumber;
Next, you supply a prompt and use the command Convert.ToDouble() on a string obtained by Console.ReadLine() to convert the string into a double type. You assign this number to the firstNumber variable you have declared:
Console.WriteLine(“Now give me a number:”);
firstNumber = Convert.ToDouble(Console.ReadLine());
This syntax is remarkably simple, and many other conversions can be performed in a similar way.
The remainder of the code obtains a second number in the same way:
Console.WriteLine(“Now give me another number:”);
secondNumber = Convert.ToDouble(Console.ReadLine());
Next, you output the results of adding, subtracting, multiplying, and dividing the two numbers, in addition to displaying the remainder after division, using the remainder (%) operator:
Console.WriteLine(“The sum of {0} and {1} is {2}.”, firstNumber,
secondNumber, firstNumber + secondNumber);
Console.WriteLine(“The result of subtracting {0} from {1} is {2}.”,
secondNumber, firstNumber, firstNumber—secondNumber);
Console.WriteLine(“The product of {0} and {1} is {2}.”, firstNumber,
secondNumber, firstNumber * secondNumber);
Console.WriteLine(“The result of dividing {0} by {1} is {2}.”,
firstNumber, secondNumber, firstNumber / secondNumber);
Console.WriteLine(“The remainder after dividing {0} by {1} is {2}.”,
firstNumber, secondNumber, firstNumber % secondNumber);
Note that you are supplying the expressions, firstNumber + secondNumber and so on, as a parameter to the Console.WriteLine() statement, without using an intermediate variable:
Console.WriteLine(“The sum of {0} and {1} is {2}.”, firstNumber,
secondNumber, firstNumber + secondNumber);
This kind of syntax can make your code very readable, and reduce the number of lines of code you need to write.
Assignment Operators
So far, you’ve been using the simple = assignment operator, and it may come as a surprise that any other assignment operators exist at all. There are more, however, and the biggest surprise is probably that they’re quite useful! All of the assignment operators other than = work in a similar way. Like =, they all result in a value being assigned to the variable on their left side based on the operands and operators on their right side.
The following table describes the operators:
Operator |
Category |
Example Expression |
Result |
= |
Binary |
var1 = var2; |
var1 is assigned the value of var2. |
+= |
Binary |
var1 += var2; |
var1 is assigned the value that is the sum of var1 and var2. |
-= |
Binary |
var1 -= var2; |
var1 is assigned the value that is the value of var2 subtracted from the value of var1. |
*= |
Binary |
var1 *= var2; |
var1 is assigned the value that is the product of var1 and var2. |
/= |
Binary |
var1 /= var2; |
var1 is assigned the value that is the result of dividing var1 by var2. |
%= |
Binary |
var1 %= var2; |
var1 is assigned the value that is the remainder when var1 is divided by var2. |
As you can see, the additional operators result in var1 being included in the calculation, so code like
var1 += var2;
has exactly the same result as
var1 = var1 + var2;
Note
The += operator can also be used with strings, just like +.
Using these operators, especially when employing long variable names, can make code much easier to read.
Operator Precedence
When an expression is evaluated, each operator is processed in sequence, but this doesn’t necessarily mean evaluating these operators from left to right. As a trivial example, consider the following:
var1 = var2 + var3;
Here, the + operator acts before the = operator. There are other situations where operator precedence isn’t so obvious, as shown here:
var1 = var2 + var3 * var4;
In the preceding example, the * operator acts first, followed by the + operator, and finally the = operator. This is standard mathematical order, and it provides the same result as you would expect from working out the equivalent algebraic calculation on paper.
Similarly, you can gain control over operator precedence by using parentheses, as shown in this example:
var1 = (var2 + var3) * var4;
Here, the content of the parentheses is evaluated first, meaning that the + operator acts before the * operator.
The following table shows the order of precedence for the operators you’ve encountered so far, whereby operators of equal precedence (such as * and /) are evaluated from left to right:
Precedence |
Operators |
Highest |
++,––(used as prefixes); +,––(unary) |
*, /, % |
|
+, - |
|
=, *=, /=, %=, +=, -= |
|
Lowest |
++,––(used as suffixes) |
Note
You can use parentheses to override this precedence order, as described previously. In addition, note that ++ and --, when used as suffixes, only have lowest priority in conceptual terms, as described in the table. They don’t operate on the result of, say, an assignment expression, so you can consider them to have a higher priority than all other operators. However, because they change the value of their operand after expression evaluation, it’s easier to think of their precedence as shown in the preceding table.
Namespaces
Before moving on, it’s worthwhile to consider one more important subject—namespaces. These are the .NET way of providing containers for application code, such that code and its contents may be uniquely identified. Namespaces are also used as a means of categorizing items in the .NET Framework. Most of these items are type definitions, such as the simple types in this chapter (System.Int32 and so on).
C# code, by default, is contained in the global namespace. This means that items contained in this code are accessible from other code in the global namespace simply by referring to them by name. You can use the namespace keyword, however, to explicitly define the namespace for a block of code enclosed in curly brackets. Names in such a namespace must be qualified if they are used from code outside of this namespace.
A qualified name is one that contains all of its hierarchical information, which basically means that if you have code in one namespace that needs to use a name defined in a different namespace, you must include a reference to this namespace. Qualified names use period characters (.) between namespace levels, as shown here:
namespace LevelOne
{
// code in LevelOne namespace
// name “NameOne” defined
}
// code in global namespace
This code defines one namespace, LevelOne, and a name in this namespace, NameOne (no actual code is shown here to keep the discussion general; instead, a comment appears where the definition would go). Code written inside the LevelOne namespace can simply refer to this name using NameOne—no classification is necessary. Code in the global namespace, however, must refer to this name using the classified name LevelOne.NameOne.
Within a namespace, you can define nested namespaces, also using the namespace keyword. Nested namespaces are referred to via their hierarchy, again using periods to classify each level of the hierarchy. This is best illustrated with an example. Consider the following namespaces:
namespace LevelOne
{
// code in LevelOne namespace
namespace LevelTwo
{
// code in LevelOne.LevelTwo namespace
// name “NameTwo” defined
}
}
// code in global namespace
Here, NameTwo must be referred to as LevelOne.LevelTwo.NameTwo from the global namespace, LevelTwo.NameTwo from the LevelOne namespace, and NameTwo from the LevelOne.LevelTwo namespace.
The important point here is that names are uniquely defined by their namespace. You could define the name NameThree in the LevelOne and LevelTwo namespaces:
namespace LevelOne
{
// name “NameThree” defined
namespace LevelTwo
{
// name “NameThree” defined
}
}
This defines two separate names, LevelOne.NameThree and LevelOne.LevelTwo.NameThree, which can be used independently of each other.
After namespaces are set up, you can use the using statement to simplify access to the names they contain. In effect, the using statement says, “OK, I’ll be needing names from this namespace, so don’t bother asking me to classify them every time.” For example, the following code says that code in the LevelOne namespace should have access to names in the LevelOne.LevelTwo namespace without classification:
namespace LevelOne
{
using LevelTwo;
namespace LevelTwo
{
// name “NameTwo” defined
}
}
Code in the LevelOne namespace can now refer to LevelTwo.NameTwo by simply using NameTwo.
Sometimes, as with the NameThree example shown previously, this can lead to problems with clashes between identical names in different namespaces (if you use such a name, then your code won’t compile—and the compiler will let you know that there is an ambiguity). In cases such as these, you can provide an alias for a namespace as part of the using statement:
namespace LevelOne
{
using LT = LevelTwo;
// name “NameThree” defined
namespace LevelTwo
{
// name “NameThree” defined
}
}
Here, code in the LevelOne namespace can refer to LevelOne.NameThree as NameThree and LevelOne.LevelTwo.NameThree as LT.NameThree.
using statements apply to the namespace they are contained in, and any nested namespaces that might also be contained in this namespace. In the preceding code, the global namespace can’t use LT.NameThree. However, if this using statement were declared as
using LT = LevelOne.LevelTwo;
namespace LevelOne
{
// name “NameThree” defined
namespace LevelTwo
{
// name “NameThree” defined
}
}
then code in the global namespace and the LevelOne namespace can use LT.NameThree.
Note one more important point here: The using statement doesn’t in itself give you access to names in another namespace. Unless the code in a namespace is in some way linked to your project, by being defined in a source file in the project or being defined in some other code linked to the project, you won’t have access to the names contained. In addition, if code containing a namespace is linked to your project, then you have access to the names contained in that code, regardless of whether you use using. using simply makes it easier for you to access these names and can shorten otherwise lengthy code to make it more readable.
Going back to the code in ConsoleApplication1 shown at the beginning of this chapter, the following lines that apply to namespaces appear:
using System;
using System.Collections.Generic;
using System.Text;
namespace ConsoleApplication1
{
.
}
The three lines that start with the using keyword are used to declare that the System, System.Collections.Generic, and System.Text namespaces will be used in this C# code and should be accessible from all namespaces in this file without classification. The System namespace is the root namespace for .NET Framework applications and contains all the basic functionality you need for console applications. The other two namespaces are very often used in console applications, so they are there just in case.
Finally, a namespace is declared for the application code itself, ConsoleApplication1.
Summary
In this chapter, you covered a fair amount of ground on the way to creating usable (if basic) C# applications. You’ve looked at the basic C# syntax and analyzed the basic console application code that VS and VCE generate for you when you create a console application project.
The major part of this chapter concerned the use of variables. You have seen what variables are, how you create them, how you assign values to them, and how you manipulate them and the values that they contain. Along the way, you’ve also looked at some basic user interaction, which showed how you can output text to a console application and read user input back in. This involved some very basic type conversion, a complex subject that is covered in more depth in Chapter 5.
You also learned how you can assemble operators and operands into expressions, and looked at the way these are executed and the order in which this takes place.
Finally, you looked at namespaces, which will become increasingly important as the book progresses. By introducing this topic in a fairly abstract way here, the groundwork is completed for later discussions.
This chapter covered all of the following:
How basic C# syntax works
What Visual Studio does when you create a console application project
How to understand and use variables
How to understand and use expressions
What a namespace is
So far, all of your programming has taken the form of line-by-line execution. In the next chapter, you learn how to make your code more efficient by controlling the flow of execution using looping techniques and conditional branching.
Exercises
- In the following code, how would you refer to the name great from code in the namespace fabulous?
namespace fabulous
{
// code in fabulous namespace
}
namespace super
{
namespace smashing
{
// great name defined
}
}
Which of the following is not a legal variable name:
myVariableIsGood
99Flake
_floor
time2GetJiggyWidIt
wrox.com
Is the string “supercalifragilisticexpialidocious” too big to fit in a string variable? If so, why?
By considering operator precedence, list the steps involved in the computation of the following expression:
resultVar += var1 * var2 + var3 % var4 / var5;
- Write a console application that obtains four int values from the user and displays the product. Hint: You may recall that the Convert.ToDouble() command was used to convert the input from the console to a double; the equivalent command to convert from a string to an int is Convert.ToInt32().
Beginning Microsoft® Visual C#® 2008, Copyright © 2008 by Wiley Publishing, Inc., ISBN: 978-0-470-19135-4, Published by Wiley Publishing, Inc., All Rights Reserved. Wrox, the Wrox logo, Wrox Programmer to Programmer, and related trade dress are trademarks or registered trademarks of John Wiley & Sons, Inc and/or its affiliates.