Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro CSharp 2008 And The .NET 3.5 Platform [eng]

.pdf
Скачиваний:
71
Добавлен:
16.08.2013
Размер:
22.5 Mб
Скачать

102 CHAPTER 3 CORE C# PROGRAMMING CONSTRUCTS, PART I

userIsDone = Console.ReadLine(); Console.WriteLine("In while loop");

}

}

Closely related to the while loop is the do/while statement. Like a simple while loop, do/while is used when you need to perform some action an undetermined number of times. The difference is that do/while loops are guaranteed to execute the corresponding block of code at least once (in contrast, it is possible that a simple while loop may never execute if the terminating condition is false from the onset).

static void ExecuteDoWhileLoop()

{

string userIsDone = "";

do

{

Console.WriteLine("In do/while loop"); Console.Write("Are you done? [yes] [no]: "); userIsDone = Console.ReadLine();

}while(userIsDone.ToLower() != "yes"); // Note the semicolon!

}

Decision Constructs and the Relational/Equality

Operators

Now that you can iterate over a block of statements, the next related concept is how to control the flow of program execution. C# defines two simple constructs to alter the flow of your program, based on various contingencies:

The if/else statement

The switch statement

The if/else Statement

First up is our good friend the if/else statement. Unlike in C and C++, however, the if/else statement in C# operates only on Boolean expressions, not ad hoc values such as –1, 0. Given this, if/else statements typically involve the use of the C# operators shown in Table 3-7 in order to obtain a literal Boolean value.

Table 3-7. C# Relational and Equality Operators

C# Equality/Relational

 

Meaning in Life

Operator

Example Usage

==

if(age == 30)

Returns true only if each expression is the

 

 

same

!=

if("Foo" != myStr)

Returns true only if each expression is different

<

if(bonus < 2000)

Returns true if expression A is less than,

>

if(bonus > 2000)

greater than, less than or equal to, or greater

<=

if(bonus <= 2000)

than or equal to expression B

>=

if(bonus >= 2000)

 

 

 

 

CHAPTER 3 CORE C# PROGRAMMING CONSTRUCTS, PART I

103

Again, C and C++ programmers need to be aware that the old tricks of testing a condition for a value “not equal to zero” will not work in C#. Let’s say you want to see whether the string you are working with is longer than zero characters. You may be tempted to write

static void ExecuteIfElse()

{

// This is illegal, given that Length returns an int, not a bool. string stringData = "My textual data";

if(stringData.Length)

{

Console.WriteLine("string is greater than 0 characters");

}

}

If you wish to make use of the String.Length property to determine truth or falsity, you need to modify your conditional expression to resolve to a Boolean. For example:

// Legal, as this resolves to either true or false. if(stringData.Length > 0)

{

Console.WriteLine("string is greater than 0 characters");

}

An if statement may be composed of complex expressions as well and can contain else statements to perform more complex testing. The syntax is identical to C(++) and Java (and not too far removed from Visual Basic). To build complex expressions, C# offers an expected set of conditional operators, as shown in Table 3-8.

Table 3-8. C# Conditional Operators

Operator

Example

 

 

 

 

Meaning in Life

&&

if((age

== 30)

&&

(name

==

"Fred"))

Conditional AND operator

||

if((age

== 30)

||

(name

==

"Fred"))

Conditional OR operator

!

if(!myBool)

 

 

 

 

Conditional NOT operator

 

 

 

 

 

 

 

 

The switch Statement

The other simple selection construct offered by C# is the switch statement. As in other C-based languages, the switch statement allows you to handle program flow based on a predefined set of choices. For example, the following Main() logic prints a specific string message based on one of two possible selections (the default case handles an invalid selection):

// Switch on a numerical value. static void ExecuteSwitch()

{

Console.WriteLine("1 [C#], 2 [VB]"); Console.Write("Please pick your language preference: ");

string langChoice = Console.ReadLine(); int n = int.Parse(langChoice);

switch (n)

{

case 1:

104 CHAPTER 3 CORE C# PROGRAMMING CONSTRUCTS, PART I

Console.WriteLine("Good choice, C# is a fine language."); break;

case 2:

Console.WriteLine("VB .NET: OOP, multithreading, and more!"); break;

default:

Console.WriteLine("Well...good luck with that!"); break;

}

}

Note C# demands that each case (including default) that contains executable statements have a terminating break or goto to avoid fall-through.

One nice feature of the C# switch statement is that you can evaluate string data in addition to numeric data. Here is an updated switch statement that does this very thing (notice we have no need to parse the user data into a numeric value with this approach):

static void ExecuteSwitchOnString()

{

Console.WriteLine("C# or VB");

Console.Write("Please pick your language preference: ");

string langChoice = Console.ReadLine(); switch (langChoice)

{

case "C#":

Console.WriteLine("Good choice, C# is a fine language."); break;

case "VB":

Console.WriteLine("VB .NET: OOP, multithreading and more!"); break;

default:

Console.WriteLine("Well...good luck with that!"); break;

}

}

Source Code The IterationsAndDecisions project is located under the Chapter 3 subdirectory.

Summary

The goal of this chapter was to expose you to numerous core aspects of the C# programming language. Here, we examined the constructs that will be commonplace in any application you may be interested in building. After examining the role of an application object, you learned that every C# executable program must have a type defining a Main() method, which serves as the program’s entry point. Within the scope of Main(), you typically create any number of objects that work together to breathe life into your application.

CHAPTER 3 CORE C# PROGRAMMING CONSTRUCTS, PART I

105

Next, we dove into the details of the built-in data types of C#, and came to understand that each data type keyword (e.g., int) is really a shorthand notation for a full-blown type in the System namespace (System.Int32 in this case). Given this, each C# data type has a number of built-in members. Along the same vein, you also learned about the role of widening and narrowing as well as the role of the checked and unchecked keywords.

We wrapped up by checking out the various iteration and decision constructs supported by C#. Now that you have an understanding of some of the basic nuts and bolts, the next chapter completes our examination of core language features. After this point, you will be well prepared to examine the object-oriented features of C#.

C H A P T E R 4

Core C# Programming Constructs,

Part II

This chapter picks up where the previous chapter left off, and completes your investigation of the core aspects of the C# programming language. We begin by examining various details regarding the construction of C# methods, exploring the out, ref, and params keywords along the way. Once you examine the topic of method overloading, the next task is to investigate the details behind manipulating array types using the syntax of C# and get to know the functionality contained within the related System.Array class type.

In addition, this chapter provides a discussion regarding the construction of enumeration and structure types, including a fairly detailed examination of the distinction between a value type and a reference type. We wrap this up by examining the role of nullable data types and the ? and ?? operators.

Methods and Parameter Modifiers

To begin this chapter, let’s examine the details of defining type methods. Just like the Main() method (see Chapter 3), your custom methods may or may not take parameters and may or may not return values. As you will see over the next several chapters, methods can be implemented within the scope of classes or structures (and prototyped within interface types) and may be decorated with various keywords (internal, virtual, public, new, etc.) to qualify their behavior. At this point in the text, each of our methods has followed this basic format:

//Recall that static methods can be called directly

//without creating a class instance.

class Program

{

// static returnVal MethodName(args) {...} static int Add(int x, int y){ return x + y; }

}

While the definition of a method in C# is quite straightforward, there are a handful of keywords that you can use to control how arguments are passed to the method in question, and these are listed in Table 4-1.

107

108 CHAPTER 4 CORE C# PROGRAMMING CONSTRUCTS, PART II

Table 4-1. C# Parameter Modifiers

Parameter Modifier

Meaning in Life

(None)

If a parameter is not marked with a parameter modifier, it is assumed to be

 

passed by value, meaning the called method receives a copy of the original

 

data.

out

Output parameters must be assigned by the method being called (and

 

therefore are passed by reference). If the called method fails to assign

 

output parameters, you are issued a compiler error.

ref

The value is initially assigned by the caller and may be optionally reassigned

 

by the called method (as the data is also passed by reference). No compiler

 

error is generated if the called method fails to assign a ref parameter.

params

This parameter modifier allows you to send in a variable number of

 

arguments as a single logical parameter. A method can have only a single

 

params modifier, and it must be the final parameter of the method.

 

 

To illustrate the use of these keywords, create a new Console Application project named FunWithMethods. Now, let’s walk through the role of each keyword in turn.

The Default Parameter-Passing Behavior

The default manner in which a parameter is sent into a function is by value. Simply put, if you do not mark an argument with a parameter-centric modifier, a copy of the data is passed into the function. As explained at the end of this chapter, exactly what is copied will depend on whether the parameter is a value type or a reference type. For the time being, assume the following method within the Program class that operates on two numerical data types passed by value:

// Arguments are passed by value by default. static int Add(int x, int y)

{

int ans = x + y;

//Caller will not see these changes

//as you are modifying a copy of the

//original data.

x = 10000; y = 88888; return ans;

}

Numerical data falls under the category of value types. Therefore, if you change the values of the parameters within the scope of the member, the caller is blissfully unaware, given that you are changing the values on a copy of the caller’s data:

static void Main(string[] args)

{

Console.WriteLine("***** Fun with Methods *****");

// Pass two variables in by value. int x = 9, y = 10;

Console.WriteLine("Before call: X: {0}, Y: {1}", x, y); Console.WriteLine("Answer is: {0}", Add(x, y)); Console.WriteLine("After call: X: {0}, Y: {1}", x, y); Console.ReadLine();

}

CHAPTER 4 CORE C# PROGRAMMING CONSTRUCTS, PART II

109

As you would hope, the values of x and y remain identical before and after the call to Add(), as shown in Figure 4-1.

Figure 4-1. By default, parameters are passed by value.

The out Modifier

Next, we have the use of output parameters. Methods that have been defined to take output parameters (via the out keyword) are under obligation to assign them to an appropriate value before exiting the method in question (if you fail to do so, you will receive compiler errors).

To illustrate, here is an alternative version of the Add() method that returns the sum of two integers using the C# out modifier (note the physical return value of this method is now void):

// Output parameters must be assigned by the called method. static void Add(int x, int y, out int ans)

{

ans = x + y;

}

Calling a method with output parameters also requires the use of the out modifier. Recall that local variables passed as output variables are not required to be assigned before use (if you do so, the original value is lost after the call), for example:

static void Main(string[] args)

{

Console.WriteLine("***** Fun with Methods *****");

...

//No need to assign initial value to local variables

//used as output parameters.

int ans;

Add(90, 90, out ans); Console.WriteLine("90 + 90 = {0}", ans); Console.ReadLine();

}

The previous example is intended to be illustrative in nature; you really have no reason to return the value of your summation using an output parameter. However, the C# out modifier does serve a very useful purpose: it allows the caller to obtain multiple return values from a single method invocation.

// Returning multiple output parameters.

static void FillTheseValues(out int a, out string b, out bool c)

{

a = 9;

b = "Enjoy your string."; c = true;

}

110 CHAPTER 4 CORE C# PROGRAMMING CONSTRUCTS, PART II

The caller would be able to invoke the following method:

static void Main(string[] args)

{

Console.WriteLine("***** Fun with Methods *****");

...

int i; string str; bool b;

FillTheseValues(out i, out str, out b); Console.WriteLine("Int is: {0}", i); Console.WriteLine("String is: {0}", str); Console.WriteLine("Boolean is: {0}", b); Console.ReadLine();

}

Finally, remember that methods that define output parameters must assign the parameter to a valid value before exiting the methods. Therefore, the following method will result in a compiler error, as the integer parameter has not been assigned within the method scope:

static void ThisWontCompile(out int a)

{

Console.WriteLine("This is an error...");

}

The ref Modifier

Now consider the use of the C# ref parameter modifier. Reference parameters are necessary when you wish to allow a method to operate on (and usually change the values of) various data points declared in the caller’s scope (such as a sorting or swapping routine). Note the distinction between output and reference parameters:

Output parameters do not need to be initialized before they passed to the method. The reason for this? The method must assign output parameters before exiting.

Reference parameters must be initialized before they are passed to the method. The reason for this? You are passing a reference to an existing variable. If you don’t assign it to an initial value, that would be the equivalent of operating on an unassigned local variable.

Let’s check out the use of the ref keyword by way of a method that swaps two strings:

// Reference parameters.

public static void SwapStrings(ref string s1, ref string s2)

{

string tempStr = s1; s1 = s2;

s2 = tempStr;

}

This method can be called as follows:

static void Main(string[] args)

{

Console.WriteLine("***** Fun with Methods *****");

...

string s1 = "Flip"; string s2 = "Flop";

Console.WriteLine("Before: {0}, {1} ", s1, s2); SwapStrings(ref s1, ref s2);

CHAPTER 4 CORE C# PROGRAMMING CONSTRUCTS, PART II

111

Console.WriteLine("After: {0}, {1} ", s1, s2); Console.ReadLine();

}

Here, the caller has assigned an initial value to local string data (s and s2). Once the call to SwapStrings() returns, s1 now contains the value "Flop", while s2 reports the value "Flip" (see Figure 4-2).

Figure 4-2. Reference parameters can be changed by the called method.

Note The C# ref keyword will be revisited later in this chapter in the section “Understanding Value Types and Reference Types.” As you will see, the behavior of this keyword changes just a bit depending on whether the argument is a “value type” (structure) or “reference type” (class).

The params Modifier

Last but not least, C# supports the use of parameter arrays. To understand the role of the params keyword, you must (as the name implies) understand how to manipulate C# arrays. If this is not the case, you may wish to return to this section once you have finished this chapter, as we will formally examine the System.Array type a bit later in this chapter in the section “Array Manipulation in C#.”

The params keyword allows you to pass into a method a variable number of parameters (of the same type) as a single logical parameter. As well, arguments marked with the params keyword can be processed if the caller sends in a strongly typed array or a comma-delimited list of items. Yes, this can be confusing! To clear things up, assume you wish to create a function that allows the caller to pass in any number of arguments and return the calculated average.

If you were to prototype this method to take an array of doubles, this would force the caller to first define the array, then fill the array, and finally pass it into the method. However, if you define CalculateAverage() to take a params of integer data types, the caller can simply pass a commadelimited list of doubles. The .NET runtime will automatically package the set of doubles into an array of type double behind the scenes:

// Return average of "some number" of doubles.

static double CalculateAverage(params double[] values)

{

Console.WriteLine("You sent me {0} doubles.", values.Length);

double sum = 0; if(values.Length == 0)

return sum;

for (int i = 0; i < values.Length; i++) sum += values[i];

return (sum / values.Length);

}