Answers to Exercises - Beginning Object-Oriented Programming with C# (2012)

Beginning Object-Oriented Programming with C# (2012)

Appendix

Answers to Exercises

Chapter 2

Exercise 1 Solution

A class is a body of code that contains properties and methods that describe an object. An object is an instance of a class that can be used in a program. Classes and objects are related in that a class is a template from which an object is created. They are different in that a class is not directly used in a program, whereas an object is.

Exercise 2 Solution

The Five Program Steps:

· The Initialization step creates the environment in which the program is run.

· The Input step is responsible for collecting all the information necessary for the program to accomplish its given task.

· The Process step takes the data and acts on it in a way that accomplishes a given task.

· The Output step presents the results of the Process step in a way that is useful to the user.

· The Termination step is responsible for placing the program environment back to the state that prevailed before the program was run, updating any environment information that might be used the next time the program is run.

Exercise 3 Solution

The first statement defines a reference variable that you can use to access a clsThingie object. The second statement instantiates a clsThingie object and makes its properties and methods available through the reference variable named myThingie.

Exercise 4 Solution

A simple oven might have the following properties and methods:

// ================ Properties ===================

int ovenTemp; // 0 is off, otherwise desired temperature

int ovenOnOff;

int ovenLight; // 0 is off, 1 is on

int frontLeftBurner; // 0 is off, otherwise its temperature

int frontRightBurner;

int backLeftBurner;

int backRightBurner;

int minutesToBake; // Oven timer

// ================ Methods ===================

public int TurnOvenOn(int ovenTemp);

public int TurnOvenOff();

public int SetTimer(int minutes);

public int TurnFrontLeftBurnerOnOff(int front LeftBurner);

public int TurnFrontRightBurnerOnOff(int frontRightBurner);

public int TurnBackLeftBurnerOnOff(int backLeftBurner);

public int TurnBackRightBurnerOnOff(int backRightBurner);

public int OverLight(int ovenLight);

private int ReadTimer();

public int SelfClean(); // Set ovenTemp to MAX for 1 hour and shuts off

Exercise 5 Solution

A good interface is one that:

· Is intuitive to the user

· Gets the required information from the user easily

· Handles user input errors in a graceful manner

Exercise 6 Solution

There is no correct solution to the question of why you are making the effort to learn OOP, but you will probably find that answering this question is beneficial.

Exercise 7 Solution

For example, click frmMain in the Design view and change the form's BackColor to a color and see what happens.

Chapter 3

Exercise 1 Solution

The unsigned data types have the capability to essentially double the maximum positive number that a given unsigned data type can hold relative to its signed counterpart. The unsigned data attribute is only applicable to integral data types.

Exercise 2 Solution

All are legal except the following:

· 9WaysToSunday: Illegal because it starts with a number

· Ms.Mini: Illegal because it has a punctuation character (a period) in it

· Extern: Illegal because it is a keyword in C#

· My#Eye: Illegal because it has a reserved symbol (#) in it

Exercise 3 Solution

It appears that txtInput is a textbox object. The first dot operator gains you access to the Text object. Because Text is a String object, the next dot operator enables you to access the Length property of the Text object. Therefore, Length tells you how long the text string is in the textbox. That value is then assigned into len.

Exercise 4 Solution

First, the float data type uses 4 bytes of storage, whereas a double uses 8 bytes. Second, the range of numbers for a double is almost 10 times larger than for a float. Third, a float has only 7 digits of precision, whereas a double has 15.

Exercise 5 Solution

const int SNAKEEYES = 2; // Minimum value for two die

const int BOXCARS = 12; // Maximum value for two die

int ThrowDice()

{

Random rnd = new Random();

// Add 1 because it's exclusive

return rnd.Next(SNAKEEYES, BOXCARS + 1);

}

Exercise 6 Solution

Use the decimal data type with its 28 digits of precision versus 15 for a double.

Chapter 4

Exercise 1 Solution

You can set a breakpoint at most points in the source code file. However, you cannot set a breakpoint on an empty line, on a line that uses the using keyword, or on a line that begins with a comment character. There may be other contexts in which a breakpoint cannot be set.

Exercise 2 Solution

The code for the button click event is:

private void btnCalc_Click(object sender, EventArgs e)

{

bool flag;

double operand1;

double answer;

// Input Step

// Check first input...

flag = double.TryParse(txtOperand1.Text, out operand1);

if (flag == false)

{

MessageBox.Show(”Enter Fahrenheit temperature”, “Input Error”);

txtOperand1.Focus();

return;

}

// Process Step

answer = 5.0 / 9.0 * (operand1 - 32);

// Display Step

txtResult.Text = operand1.ToString() + “ is “ +

answer.ToString() + “ Celsius”;

txtResult.Visible = true;

}

There are three magic numbers in this code, and you can replace them with:

const double RATIO5OVER9 = .555555555555555; // 5.0 / 9.0 in equation

const double FREEZEWATER = 32.0;

The equation then becomes:

answer = RATIO5OVER9 * (operand1 - FREEZEWATER);

The only advantage of the first constant is that division is slow, and in a tight loop, the constant avoids the divide operation. Also, the constant makes it easier to read and understand than just a magic number. The second comment simply defines the freezing point of water.

Exercise 3 Solution

An lvalue is the memory address of a given variable that has been defined. The rvalue is what is stored at that memory address. If a variable has an lvalue that is null, that variable is not defined and therefore the entry is simply an attribute list for the data item. This is a data declaration. A non- nulllvalue means the variable is defined. In this case, the data item has an attribute list, but exists in memory and is defined for use in the program.

Exercise 4 Solution

The size of the bucket depends upon the type of data that is defined. The bucket must be large enough to hold the number of bytes associated with the data item. The key elements of the Bucket Analogy are:

· The memory location of the bucket is the data item's lvalue.

· The contents of the bucket are the data item's rvalue.

· The size of the bucket equals the number of bytes necessary to store the data item in memory.

Exercise 5 Solution

The code could be written as:

using System;

using System.Windows.Forms;

public class frmMain : Form

{

private TextBox txtOperand1;

private TextBox txtOperand2;

private Label label2;

private Button btnCalc;

private Button btnExit;

private TextBox txtResult;

private Label label1;

#region Windows code

private void InitializeComponent()

{

this.label1 = new System.Windows.Forms.Label();

this.txtOperand1 = new System.Windows.Forms.TextBox();

this.txtOperand2 = new System.Windows.Forms.TextBox();

this.label2 = new System.Windows.Forms.Label();

this.btnCalc = new System.Windows.Forms.Button();

this.btnExit = new System.Windows.Forms.Button();

this.txtResult = new System.Windows.Forms.TextBox();

this.SuspendLayout();

//

// label1

//

this.label1.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;

this.label1.Location = new System.Drawing.Point(5, 22);

this.label1.Name = “label1”;

this.label1.Size = new System.Drawing.Size(169, 20);

this.label1.TabIndex = 0;

this.label1.Text = “Item price:”;

this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleRight;

//

// txtOperand1

//

this.txtOperand1.Location = new System.Drawing.Point(180, 22);

this.txtOperand1.Name = “txtOperand1”;

this.txtOperand1.Size = new System.Drawing.Size(100, 20);

this.txtOperand1.TabIndex = 1;

//

// txtOperand2

//

this.txtOperand2.Location = new System.Drawing.Point(180, 48);

this.txtOperand2.Name = “txtOperand2”;

this.txtOperand2.Size = new System.Drawing.Size(100, 20);

this.txtOperand2.TabIndex = 3;

//

// label2

//

this.label2.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;

this.label2.Location = new System.Drawing.Point(5, 47);

this.label2.Name = “label2”;

this.label2.Size = new System.Drawing.Size(169, 20);

this.label2.TabIndex = 2;

this.label2.Text = “Number of units purchased:”;

this.label2.TextAlign = System.Drawing.ContentAlignment.MiddleRight;

//

// btnCalc

//

this.btnCalc.Location = new System.Drawing.Point(5, 120);

this.btnCalc.Name = “btnCalc”;

this.btnCalc.Size = new System.Drawing.Size(75, 23);

this.btnCalc.TabIndex = 4;

this.btnCalc.Text = “&Calculate”;

this.btnCalc.UseVisualStyleBackColor = true;

this.btnCalc.Click += new System.EventHandler(this.btnCalc_Click);

//

// btnExit

//

this.btnExit.Location = new System.Drawing.Point(205, 120);

this.btnExit.Name = “btnExit”;

this.btnExit.Size = new System.Drawing.Size(75, 23);

this.btnExit.TabIndex = 5;

this.btnExit.Text = “E&xit”;

this.btnExit.UseVisualStyleBackColor = true;

this.btnExit.Click += new System.EventHandler(this.btnExit_Click);

//

// txtResult

//

this.txtResult.Location = new System.Drawing.Point(5, 82);

this.txtResult.Name = “txtResult”;

this.txtResult.ReadOnly = true;

this.txtResult.Size = new System.Drawing.Size(275, 20);

this.txtResult.TabIndex = 6;

this.txtResult.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;

this.txtResult.Visible = false;

//

// frmMain

//

this.ClientSize = new System.Drawing.Size(292, 165);

this.Controls.Add(this.txtResult);

this.Controls.Add(this.btnExit);

this.Controls.Add(this.btnCalc);

this.Controls.Add(this.txtOperand2);

this.Controls.Add(this.label2);

this.Controls.Add(this.txtOperand1);

this.Controls.Add(this.label1);

this.Name = “frmMain”;

this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;

this.Text = “Figure Sales Tax Due”;

this.ResumeLayout(false);

this.PerformLayout();

}

#endregion

const decimal SALESTAXRATE = .06M; // Sales tax rate for state

public frmMain()

{

InitializeComponent();

txtResult.Visible = false;

}

public static void Main()

{

frmMain main = new frmMain();

Application.Run(main);

}

private void btnCalc_Click(object sender, EventArgs e)

{

bool flag;

decimal operand1;

decimal operand2;

decimal answer;

decimal salesTax;

flag = decimal.TryParse(txtOperand1.Text, out operand1);

if (flag == false) {

MessageBox.Show(”Enter a whole number”, “Input Error”);

txtOperand1.Focus();

return;

}

flag = decimal.TryParse(txtOperand2.Text, out operand2);

if (flag == false) {

MessageBox.Show(”Enter a whole number”, “Input Error”);

txtOperand2.Focus();

return;

}

answer = operand1 * operand2;

salesTax = answer * SALESTAXRATE;

txtResult.Text = “Sales tax due on sale of “ + answer.ToString() + “ units

is “ + salesTax.ToString();

txtResult.Visible = true;

}

private void btnExit_Click(object sender, EventArgs e)

{

Close();

}

}

By using SALESTAXRATE as a symbolic constant, it makes it easier to adjust the rate for other states or if the current state's sales tax rate changes.

Exercise 6 Solution

using System;

using System.Windows.Forms;

public class frmMain : Form

{

private TextBox txtAmount;

private TextBox txtMonths;

private Label label2;

private Button btnCalc;

private Button btnExit;

private TextBox txtResult;

private TextBox txtInterestRate;

private Label label3;

private Label label1;

#region Windows code

private void InitializeComponent()

{

this.label1 = new System.Windows.Forms.Label();

this.txtAmount = new System.Windows.Forms.TextBox();

this.txtMonths = new System.Windows.Forms.TextBox();

this.label2 = new System.Windows.Forms.Label();

this.btnCalc = new System.Windows.Forms.Button();

this.btnExit = new System.Windows.Forms.Button();

this.txtResult = new System.Windows.Forms.TextBox();

this.txtInterestRate = new System.Windows.Forms.TextBox();

this.label3 = new System.Windows.Forms.Label();

this.SuspendLayout();

//

// label1

//

this.label1.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;

this.label1.Location = new System.Drawing.Point(5, 22);

this.label1.Name = “label1”;

this.label1.Size = new System.Drawing.Size(169, 20);

this.label1.TabIndex = 0;

this.label1.Text = “Loan amount:”;

this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleRight;

//

// txtAmount

//

this.txtAmount.Location = new System.Drawing.Point(180, 22);

this.txtAmount.Name = “txtAmount”;

this.txtAmount.Size = new System.Drawing.Size(100, 20);

this.txtAmount.TabIndex = 1;

this.txtAmount.Text = “150000”;

//

// txtMonths

//

this.txtMonths.Location = new System.Drawing.Point(180, 48);

this.txtMonths.Name = “txtMonths”;

this.txtMonths.Size = new System.Drawing.Size(100, 20);

this.txtMonths.TabIndex = 3;

this.txtMonths.Text = “360”;

//

// label2

//

this.label2.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;

this.label2.Location = new System.Drawing.Point(5, 47);

this.label2.Name = “label2”;

this.label2.Size = new System.Drawing.Size(169, 20);

this.label2.TabIndex = 2;

this.label2.Text = “Number of months for loan:”;

this.label2.TextAlign = System.Drawing.ContentAlignment.MiddleRight;

//

// btnCalc

//

this.btnCalc.Location = new System.Drawing.Point(5, 167);

this.btnCalc.Name = “btnCalc”;

this.btnCalc.Size = new System.Drawing.Size(75, 23);

this.btnCalc.TabIndex = 4;

this.btnCalc.Text = “&Calculate”;

this.btnCalc.UseVisualStyleBackColor = true;

this.btnCalc.Click += new System.EventHandler(this.btnCalc_Click);

//

// btnExit

//

this.btnExit.Location = new System.Drawing.Point(205, 167);

this.btnExit.Name = “btnExit”;

this.btnExit.Size = new System.Drawing.Size(75, 23);

this.btnExit.TabIndex = 5;

this.btnExit.Text = “E&xit”;

this.btnExit.UseVisualStyleBackColor = true;

this.btnExit.Click += new System.EventHandler(this.btnExit_Click);

//

// txtResult

//

this.txtResult.Location = new System.Drawing.Point(5, 129);

this.txtResult.Name = “txtResult”;

this.txtResult.ReadOnly = true;

this.txtResult.Size = new System.Drawing.Size(275, 20);

this.txtResult.TabIndex = 6;

this.txtResult.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;

this.txtResult.Visible = false;

//

// txtInterestRate

//

this.txtInterestRate.Location = new System.Drawing.Point(180, 74);

this.txtInterestRate.Name = “txtInterestRate”;

this.txtInterestRate.Size = new System.Drawing.Size(100, 20);

this.txtInterestRate.TabIndex = 8;

this.txtInterestRate.Text = “6”;

//

// label3

//

this.label3.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;

this.label3.Location = new System.Drawing.Point(5, 73);

this.label3.Name = “label3”;

this.label3.Size = new System.Drawing.Size(169, 20);

this.label3.TabIndex = 7;

this.label3.Text = “interest rate (e.g., .06):”;

this.label3.TextAlign = System.Drawing.ContentAlignment.MiddleRight;

//

// frmMain

//

this.ClientSize = new System.Drawing.Size(292, 222);

this.Controls.Add(this.txtInterestRate);

this.Controls.Add(this.label3);

this.Controls.Add(this.txtResult);

this.Controls.Add(this.btnExit);

this.Controls.Add(this.btnCalc);

this.Controls.Add(this.txtMonths);

this.Controls.Add(this.label2);

this.Controls.Add(this.txtAmount);

this.Controls.Add(this.label1);

this.Name = “frmMain”;

this.StartPosition =

System.Windows.Forms.FormStartPosition.CenterScreen;

this.Text = “Figure Sales Tax Due”;

this.ResumeLayout(false);

this.PerformLayout();

}

#endregion

const decimal INTERESTRATEASMONTHLYRATE = 1200.0M;

public frmMain()

{

InitializeComponent();

txtResult.Visible = false;

}

public static void Main()

{

frmMain main = new frmMain();

Application.Run(main);

}

private void btnCalc_Click(object sender, EventArgs e)

{

bool flag;

decimal amount;

decimal months;

decimal interest;

decimal denominator;

decimal payment;

flag = decimal.TryParse(txtAmount.Text, out amount);

if (flag == false) {

MessageBox.Show(”Enter a whole number”, “Input Error”);

txtAmount.Focus();

return;

}

flag = decimal.TryParse(txtMonths.Text, out months);

if (flag == false) {

MessageBox.Show(”Enter a whole number”, “Input Error”);

txtMonths.Focus();

return;

}

flag = decimal.TryParse(txtInterestRate.Text, out interest);

if (flag == false) {

MessageBox.Show(”Enter decimal number: 6 percent interest rate is 6”,

“Input Error”);

txtInterestRate.Focus();

return;

}

/*

* payment = (rate + (interestRate / (1.0 + interestrate)ˆmonths - 1)

* amount

*/

interest /= INTERESTRATEASMONTHLYRATE;

denominator = (decimal) (Math.Pow((double) (1.0M + interest),

(double) months) - 1.0D);

payment = (interest + (interest / denominator)) * amount;

txtResult.Text = “The monthly payment is: “ + payment.ToString(”C”);

txtResult.Visible = true;

}

private void btnExit_Click(object sender, EventArgs e)

{

Close();

}

}

The statement,

denominator = (decimal) (Math.Pow((double) (1.0M + interest),

(double) months) - 1.0D);

uses the cast operator to force the decimal data types to doubles for use in the Math.Pow() method because it expects its arguments to be double data types. However, because you assign the result into a decimal data type, you must cast the result from Pow() back to a decimal before the assignment can take place.

Chapter 5

Exercise 1 Solution

A value type variable is one whose rvalue is the variable's data. A reference variable has an rvalue that is the memory address of where the variable's data is to be found.

Exercise 2 Solution

message = message.Replace(”Frday”, “Friday”);

Exercise 3 Solution

The easiest way would be to search the string for two blank spaces (” “) and use the Replace() method to replace those two blank spaces with one space (” “).

Exercise 4 Solution

Whenever you want to define a new object, that object's constructor is called to create the necessary memory space for the object and to set the properties of that object to its default values (that is, either 0 or null). If you want, you can write your own constructor to override the actions of the default constructor.

Exercise 5 Solution

public int CurrentAge(string txtBirthday)

{

bool flag;

int month;

int day;

int year;

string temp = txtBirthday;

flag = int.TryParse(temp.Substring(0, 2), out month);

if (flag == false)

return -1;

flag = int.TryParse(temp.Substring(3, 2), out day);

if (flag == false)

return -1;

flag = int.TryParse(temp.Substring(6, 4), out year);

if (flag == false)

return -1;

DateTime birthdate = new DateTime(year, month, day);

DateTime currentDate = DateTime.Now;

int age = DateTime.Now.Year - birthdate.Year;

// subtract another year if we're before the

// birth day in the current year

if (DateTime.Now.Month < birthdate.Month ||

(DateTime.Now.Month == birthdate.Month &&

DateTime.Now.Day < birthdate.Day))

age--;

return age;

}

Exercise 6 Solution

clsMembers noviceMember = new clsMembers(”Indianapolis”, “IN”);

In this case, you assume the club is in Indianapolis, IN. The constructor cost would then assign the two strings to the appropriate properties at the time the object is created by the constructor. The person who is then entering the club information would not need to type in the city and state data.

Chapter 6

Exercise 1 Solution

const int MAXIMUMDISCOUNTAGE = 12;

const int MINIMUMDISCOUNTAGE = 65;

price = 6.00M;

if (age <= MAXIMUMDISCOUNTAGE || age >= MINIMUMDISCOUNTAGE)

{

price *= .5M;

}

Exercise 2 Solution

· Company style conventions for if-else statements.

· Placement of braces:

· Opening brace on if statement line or below it

· Closing if and opening else brace on same line as else

· Should single-line if or else blocks use braces at all?

· Is an if-else block the correct code choice or could some other structure (for example, a switch) be used.

Exercise 3 Solution

There aren't too many cases in which a cascading if statement is a good choice. Usually a switch block is clearer. However, there may be times when the switch value cannot be expressed as a constant. In that case, you are forced to use a cascading if statement.

Exercise 4 Solution

First, the test expression (x = y) actually uses the assignment operator rather than the (correct) relational operator (==). Second, the code probably should not have a trailing semicolon at the end of the first line. Third, the shorthand operator for multiply and assignment is *=. Finally, because the variable price is probably associated with money, the correct constant is .06M.

Exercise 5 Solution

const int SMALL = 1; // Available sizes

const int MEDIUM = 2;

const int LARGE = 3;

const decimal COSTOFSMALL = 6.0M; // Cost for given size

const decimal COSTOFMEDIUM = COSTOFSMALL + 1.0M;

const decimal COSTOFLARGE = COSTOFMEDIUM + 1.0M;

switch (size)

{

case SMALL:

price = COSTOFSMALL;

break;

case MEDIUM:

price = COSTOFMEDIUM;

break;

case LARGE:

price = COSTOFLARGE;

break;

default:

MessageBox.Show(”No such size.);

break;

}

The use of symbolic constants is not strictly necessary but can make it easier to account for any future price changes.

Exercise 6 Solution

The user interface code is left to the reader. One solution might be to call the following method:

public int ReturnDaysInMonth(int month, int year)

{

int answer;

// Assume January is month 1

int[] days = new int[] {0, 31,28,31,30,31,30,31,31,30,31,30,31};

answer = days[month];

if (month == FEBRUARY) // Assume this is defined earlier = 2

{

answer += IsLeapYear(year);

}

return answer;

{

public int IsLeapYear(int year)

{

if (year % 4 == 0 & year % 100 != 0 || year % 400 == 0)

{

return 1;

}

return 0;

}

The else statement block is not needed in IsLeapYear() because when you know it's not a leap year, you can just return 0. If it were a leap year, the return statement prevents the return 0 expression from being executed.

Chapter 7

Exercise 1 Solution

One possible solution is:

int factorial = 1;

for (i = num; i > 0; i--)

{

factorial *= i;

}

lblAnswer.Text = factorial.ToString();

In the for loop, notice how the initial state of the loop is set to the number to factorial (expression1) and how the loop control expression (expression3) uses the post-decrement operator to walk “backward” through the loop.

Exercise 2 Solution

The solution shown for Exercise 1 produces the correct answers…provided you don't enter a large number to factorial. Because factorials can produce large numbers, one problem is using an int data type for variable factorial. Even a factorial as small as 13 overflows the range of an int. It would be better to use a double to extend its range.

A second, and more subtle problem, is that the test expression, i > 0, causes the loop to execute one more time than is necessary. The reason is because the last iteration of the loop ends up multiplying the current value of factorial by 1 which, of course, has no impact on the result. You can correct this inefficiency by changing the second expression of the for loop to:

i > 1

This does away with the unnecessary loop iteration that multiplies factorial by 1.

Exercise 3 Solution

const int FOURPOUNDS = 48; // Ounces is 4 pounds

const double GRAMSPEROUNCE = 28.3495231;

int i;

double grams;

string buff;

for (i = 1; i <= FOURPOUNDS; i++)

{

grams = (double) i * GRAMSPEROUNCE;

buff = string.Format(”{0, 4} {1, 15}”, i, grams);

lstResult.Items.Add(buff);

}

You can take this code and place it in the button click event.

Exercise 4 Solution

Because the table is constructed by increasing variable i by 1 on each pass through the loop, the expression,

grams = (double) i * GRAMSPEROUNCE;

could be replaced with the simpler,

grams += GRAMSPEROUNCE;

provided you change the definition of grams to,

double grams = GRAMSPEROUNCE;

so it is initialized as part of its definition. Now you have a simple addition taking place within the loop where there used to be a multiply and a cast.

Exercise 5 Solution

Your program should input the percent and year values in the manner you used for previous programs. Because you want to use monetary values, the TryParse() method is the one for the decimal data type.

int i;

int year = 10;

decimal percent = .06M;

decimal val = 100M;

string buff;

for (i = 1; i < year; i++)

{

val *= (1.0M + percent);

buff = string.Format(”{0, 4} {1, 15:C}”, i, val);

lstResult.Items.Add(buff);

}

You used the Format() conversion for currency in the second formatting option. Of course, you wouldn't use constants in the code. The values would likely come from input textboxes.

Chapter 8

Exercise 1 Solution

The code has a user interface with two textboxes for getting the starting and ending heights for the table and a listbox or ListView object to present the results. The button click event code does most of the work, and one solution using a listbox is shown here.

const double MININCHES = 36;

const double MAXINCHES = 96;

private void btnCalc_Click(object sender, EventArgs e)

{

bool flag;

int i;

int j;

double start;

double end;

double male;

double female;

double[,] idealWeights;

string buff;

//=================== Input ==========================

flag = double.TryParse(txtStart.Text, out start); // Table start

if (flag == false)

{

MessageBox.Show(”Numeric only.”);

txtStart.Focus();

return;

}

flag = double.TryParse(txtEnd.Text, out end); // Table end

if (flag == false)

{

MessageBox.Show(”Numeric only.”);

txtEnd.Focus();

return;

}

//=================== Validate Inputs ================

if (start < MININCHES || start > MAXINCHES) // Check table limits

{

MessageBox.Show(”Table can only span “ + MININCHES.ToString() +

“ to “ + MAXINCHES + “ inches.”);

txtStart.Focus();

return;

}

if (end < MININCHES || end > MAXINCHES)

{

MessageBox.Show(”Table can only span “ + MININCHES.ToString() +

“ to “ + MAXINCHES + “ inches.”);

txtStart.Focus();

return;

}

if (end <= start) // Can we display anything?

{

MessageBox.Show(”Starting value must be less than ending value”);

txtStart.Focus();

return;

}

// Define the array for table data

idealWeights = new double[2, (int) (end - start) + 1];

//================== Process ======================

female = 3.5 * start - 108; // Set initial table values

male = 4.0 * start - 128;

for (i = (int)start, j = 0; i <= (int)end; i++, j++)

{// Since linear relationships...

idealWeights[0, j] = (female += 3.5);

idealWeights[1, j] = (male += 4.0);

}

//================== Display =======================

for (i = (int)start, j = 0; i <= (int)end; i++, j++)

{

buff = string.Format(”{0,5}{1,15}{2,15}”, i, idealWeights[0, j],

idealWeights[1, j]);

lstResults.Items.Add(buff);

}

}

The program validates the input values for the table. After the start and end values are determined, you can use those variables to set the array size:

idealWeights = new double[2, (int) (end - start) + 1];

Because start and end are double data types, you must cast those values to an int to use them to set the array size. The code then calculates the initial ideal weights for males and females. However, because the code increments the value by 1 on each pass through the loop, you can simply add 3.5 to the current female value and 4.0 to the current male value to derive the new table value. Because adding numbers is a little faster than multiplication, you get a small performance improvement. Also note how you can use the shorthand addition operators and the array reference to store the new values:

idealWeights[0, j] = (female += 3.5);

idealWeights[1, j] = (male += 4.0);

The second for loop simply displays the results in a listbox object. The code presented here is actually another RDC example because you would likely never write it this way in a production environment. Why? Because you could move everything into a single loop and even do away with the arrays if you wanted to. Think about it.

Exercise 2 Solution

Arrays of value types create a reference variable using the array name whose rvalue points to the starting memory address for the data array. Arrays of reference types, such as strings, create a reference variable using the array name whose rvalue points to an array of memory addresses, not the actual data. The memory addresses in the array point to the actual data for the reference object.

Exercise 3 Solution

One solution is:

using System;

using System.Windows.Forms;

public class frmMain : Form

{

const int MAXVAL = 52;

const int MAXELEMENTS = 100;

int[] data = new int[MAXELEMENTS];

private Button btnSort;

private Button btnClose;

private ListBox lstResult;

private Button btnCalc;

#region Windows code

public frmMain()

{

InitializeComponent();

}

public static void Main()

{

frmMain main = new frmMain();

Application.Run(main);

}

private void btnClose_Click(object sender, EventArgs e)

{

Close();

}

private void btnCalc_Click(object sender, EventArgs e)

{

int i;

Random rd = new Random(5); // Define a random object

for (i = 0; i < data.Length; i++)

{

data[i] = rd.Next(MAXVAL); // Get a random value

lstResult.Items.Add(data[i].ToString()); // Put in listbox

}

}

private void btnSort_Click(object sender, EventArgs e)

{

int i;

Array.Sort(data); // Sort the data

lstResult.Items.Clear(); // Clear out old data

for (i = 0; i < data.Length; i++) // Show it

{

lstResult.Items.Add(data[i].ToString());

}

}

}

Again, notice the use of symbolic constants. This makes it easy to change the number of items in the array. Also note that you should always use the Length property of an array to control walking through the array. That way, if you do change the array's size, the code controlling the loop does not need to be changed.

Finally, the statement,

data[i] = rd.Next(MAXVAL);

uses the Next() method of the Random class to generate a random number between 0 and MAXVAL. The set is exclusive of MAXVAL.

Exercise 4 Solution

This is actually easy to do. First, place the following definition at the top of the class:

static string stars = “*******************************************************”;

You can change the for loop code in the Calc event to:

int i;

int j;

string buff;

Random rd = new Random(5); // Define a random object

for (i = 0; i < data.Length; i++)

{

data[i] = rd.Next(MAXVAL); // Get a random value

buff = “”;

for (j = 0; j < data[i]; j++)

{

buff += “*”;

}

lstResult.Items.Add(data[i].ToString() + “ “ + buff);

}

However, a better solution is:

for (i = 0; i < data.Length; i++)

{

data[i] = rd.Next(MAXVAL); // Get a random value

buff = data[i].ToString() + “ “ + stars.Substring(0, data[i]);

lstResult.Items.Add(buff); // Put in listbox

}

You used the Substring() method of the stars string to display the proper number of stars. This is more efficient because you have done away with the inner for loop.

Exercise 5 Solution

Anytime you see an assignment statement, you should think in terms of rvalues. In this case, the rvalue of str1 is assigned into temp. What this actually means is that you now have two reference variables that point to the same array of strings.

Chapter 9

Exercise 1 Solution

The definitions would be:

//============ static members ===============

private static string[] daysOfWeek = new string[] {””, “Monday”,

“Tuesday”, “Wednesday”,”Thursday”, “Friday”, “Saturday”,

“Sunday”};

//============ instance members ===============

private string lastName;

private string zipCode;

You would want the daysOfWeek array to be static because all instances of the class could share this data. Also, we added an empty element at the front of the array because I tend to think of Monday as the first day of the week.

Exercise 2 Solution

One solution might be written as:

/*****

* Purpose: Return the number of days in a given month.

*

* Parameter list:

* int month the month “ “

* int year the year under consideration

*

* Return value:

* int the number of days in the month or 0 on error

*****/

public int getDaysInMonth(int month, int year)

{

int days;

if (month < 1 || month > 12 || year < 1 || year > 9999)

{

return 0;

}

if (month != 2) // As long as it's not February

{

days = daysInMonth[month];

}

else

{

days = daysInMonth[2] + getLeapYear(year);

}

return days;

}

A discussion of this code appears in Chapter 10.

Exercise 3 Solution

I couldn't think of one, either.

Exercise 4 Solution

First, you need to define a temporary string:

string buff;

Now replace the statement with:

buff = year.ToString() + “ is “;

if (leap == 1) // leap is 1 for a leap year

{

buff += “a leap year”;

} else

{

buff += “not a leap year”;

}

lblLeapYearResult.Text = buff;

If I were writing the program, I would use the version shown here. Although the first version tests your knowledge of the ternary operator, that is not the goal for commercial code. You should design your code so that it is as easy to read as possible. Making code easy to read makes testing and debugging easier. About the only valid reason to use complex code is when you can demonstrate that the easy-to-read code executes noticeably slower than does more complex code. If that's the case, make sure you document clearly what the complex code is doing.

Exercise 5 Solution

You would create a new getLeapYear() method that overloads the existing getLeapYear() method. The new code would be:

public int getLeapYear()

{

return getLeapYear(year);

}

Because the new constructor for clsDates can be called for the year under consideration, you can call the earlier version of getLeapYear() using the class property year as the argument. Because the two method signatures are different, Visual Studio knows which one to call based upon whether the year argument is passed.

Using a snippet of the code from Listing 9-5, you would use:

// clsDates myDate = new clsDates();

// Convert validate integer

flag = int.TryParse(txtYear.Text, out year);

if (flag == false)

{

MessageBox.Show(”Digit characters only in YYYY format.”,

“Input Error”);

txtYear.Focus();

return;

}

clsDates myDate = new clsDates(year); // Place it here!

leap = myDate.getLeapYear(); // Call overloaded method

lblLeapYearResult.Text = year.ToString() + “ is “ +

((leap == 1)? “”:”not “) + “a leap year”;

lblEasterResult.Text = myDate.getEaster(year);

With this approach, myDate is constructed passing the year variable to the constructor. You can then call the overloaded version of getLeapYear().

Chapter 10

Exercise 1 Solution

Because the only difference is in the range of cards, no changes are needed to either the client (frmMain) or the server (clsCardDeck) objects. You do, however, need to change the way each card is viewed in the rules of the game. The change is quite simple: Force each ace to have a value greater than a king. This means modifying the code for getFirstCard(), getSecondCard(), and getDealtCard() to reflect the following type of change:

public void getFirstCard()

{

lowCardIndex = myDeck.getOneCard();

lowCard = lowCardIndex % 13;

if (lowCard == 0) // A King

lowCard = 13;

if (lowCard == 1) // View an Ace as high card

lowCard = 14;

}

The last two lines of code must be added to each method, reflecting the card in question. This means that six new lines of code changes the way the game is played. No other changes are needed.

Exercise 2 Solution

The code shown in getFirstCard(), for example, has several magic numbers in it, which are usually not a good idea. Suppose you make the following changes to clsInBetweenRules:

// =============== symbolic constants ===================

const int CARDSINSUIT = 13;

const int ACE = 1;

const int KING = 13;

const int MAKEACEHIGH = 14;

// Other code in class…

public void getFirstCard()

{

lowCardIndex = myDeck.getOneCard();

lowCard = lowCardIndex % CARDSINSUIT;

if (lowCard == 0) // A King

lowCard = KING;

if (lowCard == ACE) // View an Ace as high card

lowCard = MAKEACEHIGH;

}

The symbolic constants make it a little easier to read what the method is doing. Similar changes could be made to the other magic numbers in the clsInBetweenRules class code.

Exercise 3 Solution

The way clsCardDeck is presently designed, there is no way to prevent invalid cards (for example, 2 through 8) from being dealt. Also, the current state of the deck assumes that an ace is viewed as the lowest card in a suit rather than the highest as Euchre would require.

Exercise 4 Solution

Any time you face an issue like this, you need to ask where the problem lies. The way the question is phrased, it would appear that the issue is with clsCardDeck because it deals cards that should not be used in a game of Euchre. Indeed, when I pose this question to my students, one solution that is always offered is to add a method named getOneEuchreCard() to clsCardDeck. They suggest that the method can then be written so that only valid Euchre cards are returned. Although this might work, it detracts from clsCardDeck's purpose: to deal cards from a deck. Adding Euchre functionality toclsCardDeck adds a Swiss Army knife element to the class and reduces its cohesiveness.

For example, if there are one million card games in the world and each card game's quirkiness is added to clsCardDeck, how can you possibly hope to cope with its complexity?

A little thought reveals that it is the rules of Euchre that dictates which cards are valid. It is not the responsibility of clsCardDeck to determine what rules apply to each card. The tasks for clsCardDeck remain the same: mainly, dealing a card from a shuffled deck of cards. A new class,clsEuchreRules, should be written to enforce the valid cards returned from clsCardDeck.

Exercise 5 Solution

You could replace the shuffle button code in Listing 10-5 with the following code:

private void btnShuffle_Click(object sender, EventArgs e)

{

int j;

int cardIndex;

int cardsShown;

int deckSize;

int passes;

int card;

string buff;

string temp;

clsCardDeck myDeck = new clsCardDeck();

passes = myDeck.ShuffleDeck();

lblPassCounter.Text = “It took “ + passes.ToString() +

“ passes to shuffle the deck”;

deckSize = myDeck.DeckSize;

cardIndex = 1;

cardsShown = 0;

buff = “”;

while (cardIndex < deckSize + 1)

{

card = myDeck.getCurrentCardIndex();

if (card % 13 < 9 && card % 13 != 0 && card % 13 != 1)

{

cardIndex++;

myDeck.IncrementCardIndex();

continue;

}

temp = myDeck.getOneCard(cardIndex);

buff += temp + “ “;

cardIndex++;

cardsShown++;

if (cardsShown % 6 == 0 && cardsShown > 0)

{

lstDeck.Items.Add(buff);

buff = “”;

}

}

lstDeck.Items.Add(” “); // Add an empty line

}

You also need to add two new methods to clsCardDeck:

/**

* Purpose: Get the index of current card.

*

* Parameter list:

* void

*

* Return value:

* int the index into the pips array

*/

public int getCurrentCardIndex()

{

return deck[nextCard];

}

/**

* Purpose: Advance card index to next card

*

* Parameter list:

* void

*

* Return value:

* void

*/

public void IncrementCardIndex()

{

nextCard++;

if (nextCard > DECKSIZE + 1)

{

ShuffleDeck();

}

}

Chapter 11

Exercise 1 Solution

The error message is:

Use of unassigned local variable ‘x'

The problem is that C# doesn't want you to return a variable that has not been explicitly assigned a value. In the code, x is assigned a value only when the if statement is true. To fix the problem, you must explicitly assign x a value. The easiest fix is to change the definition of x:

int x = 0;

Exercise 2 Solution

The issue here is not that you can code the equation correctly, but rather that you make it readable for others. The first solution might be,

p = a * 1 / (Math.Pow(1 + i, y));

and the code would compile and generate the correct values for p. However, which would you rather debug: the statement above or the following?

presentValue = futureAmount * 1.0 / (Math.Pow(1.0 + interestRate,

yearsIntoFuture));

The primary difference is the use of meaningful variable names. Another benefit is the use of numeric constants (1.0) that reflect that floating point values are used in the equation. Although you could make the argument that the decimal data type should be used because this is a financial calculation. However, the pow() method uses the double data type, so you would probably stuff this equation into a method that returned a double and let the user decide to cast it to a decimal if needed.

An alternative that makes debugging a bit easier would be:

discountFactor = 1.0 / (Math.Pow(1.0 + interestRate,

yearsIntoFuture));

presentValue = futureAmount * discountFactor;

This form uses an intermediate value (discountFactor) to simplify the code. This variation also enables you to generate a present value discount factor, the values for which can be found in published tables. This makes generating test data easier.

Exercise 3 Solution

You haven't used this style of variable definition because it discourages initialization and documentation of the variable. The pro is that is takes fewer keystrokes. That's not a strong enough argument to use that style. You should use:

long number = 2; // The number of parameters

long val; // The value of the solution

long len; // The length of the steel bar

The single-statement definition discourages such comments. If you insist on using the short style, you could still comment the code using the following:

long number = 2, // The number of parameters

val, // The value of the solution

len; // The length of the steel bar

Note the comma after the definition of number.

Exercise 4 Solution

This could be an example of the “forest-for-the-trees” problem. Hopefully you checked the obvious sources of error (for example, bogus input values) and those revealed nothing useful. It's not uncommon to look at the code so long that you are no longer actually reading the code closely enough to see what it does. That is, you've read the code so many times, your mind recognizes the patterns of the code, and you are convinced the bug is not in those familiar lines of code. Perhaps…perhaps not.

My first action for the “impossible bug” problem is to have someone else look at the code. It's not uncommon for another programmer to spot a program bug in minutes even though you've been staring at it for days. If you don't have another programmer you can call upon, simply take a long (at least 1 hour) break. Sometimes that gets your mind off the problem long enough so that when you come back, you're actually reading the code again.

Another technique is to force an exception just before you display the (erroneous) answer and have a catch block that does a stack trace. Because a stack trace shows the method calls that were made to generate the answer, you should use your test data set to examine each method in the order they are presented in the stack trace. (Remember, a stack trace shows the method calls in reverse order from last to first. This means you are working from the Output Step toward the Input Step for the Five Program Steps.) By hand calculating the value(s) that should be produced by each method call, you should isolate the method causing the error. Then just concentrate on what test values should be generated in each method until you isolate the bug. Correcting the bug is typically a simple process. It's the isolation process that eats up time.

Exercise 5 Solution

I've reached the point where I insist they use try-catch blocks in the Process Step of any program. That is the bare minimum. I also tell them that any method that does file or database I/O must be enclosed in a try-catch block. As a general rule, the two most dangerous places in a program where exceptions might lurk are in the Input and Process Steps of a program. I look for try-catch blocks in those two places.

I sometimes ask my students how they would rewrite their assignment if their code were going to be part of a mission-critical software package that manages the space shuttle. They almost always offer ideas of how they could have “tightened up” their code to make it less error prone in such circumstances. After they've presented all the improvements they could have made in their code, I ask them, “Which is more important to you: the next shuttle mission or your grade in this class?” They usually get the point.

There is no good reason not to sheathe each program you write in a protective coat of Kevlar whenever possible. It's just a good habit to get into.

Chapter 12

Exercise 1 Solution

The major advantage of generics is that they provide data flexibility without sacrificing strong type checking. Another advantage is that generics can avoid the overhead associated with boxing and unboxing the data. It's not uncommon for generics to provide a 100% performance increase over code that must exercise the boxing-unboxing process. Finally, the absence of generics almost always means that your code must use a lot of casts to accommodate more flexible coding practices. The “cast hassle” is avoided with generics.

Exercise 2 Solution

The first thing you would do is modify the ShowData() method along these lines:

private void ShowDataGeneric<T>(T[] val)

{

int i;

for (i = 0; i < val.Length; i++)

{

if (whichListbox == SORTED)

lstSorted.Items.Add(val[i].ToString());

else

lstUnsorted.Items.Add(val[i].ToString());

}

}

This allows you to pass in the data array that needs to be displayed. Note the use of the generic type specifier in the method's signature. Next, you would need to modify the code in the switch statements where ShowData() is used. For example, in btnSort_Click() event the code,

case INTEGER: // Integer

clsQuickSort<int> iSort = new clsQuickSort<int>(iData);

iSort.Sort();

break;

needs to add a call to the new generic method and remove the old call to ShowData(). The next code for the integer data type would be:

case INTEGER: // Integer

clsQuickSort<int> iSort = new clsQuickSort<int>(iData);

iSort.Sort();

ShowDataGeneric<int>(iData)

break;

Exercise 3 Solution

This is kind of a trick question. As we have currently discussed interfaces, they indicate a guaranteed behavior or functionality as specified in the interface. You used the known behavior of the IComparable interface in your clsQuickSort code. You don't know how that functionality is implemented, only that you can rely on it being there. The trick part of the question comes into play because you discuss other advantages of interfaces when discussing inheritance in Chapter 16.

Exercise 4 Solution

The solution means that you must pass the data to be swapped into the method. One solution is

private void swap01<T>(T[] data, int pos1, int pos2)

{

T temp;

temp = data[pos1];

data[pos1] = data[pos2];

data[pos2] = temp;

}

Exercise 5 Solution

You would hope so. Just as you mentioned the role that interfaces could play in modernizing an aircraft (for example, IControls, IAvionics, and IStunts), the same concepts apply to software versioning. By encapsulating the new features inside interface declarations, you ensure that the older versions can still function, but they simply don't have the newer enhancements available to them. This approach is especially useful when enhancements alter the underlying data structures (for example, perhaps the members of a class that are persisted to disk).

Chapter 13

Exercise 1 Solution

There's probably a bazillion different ways to accomplish this task, of which the one here involves simple changes to the ReadAndShowRecord() method:

private int ReadAndShowRecord()

{

int flag;

try

{

myData.Open(myData.FileName);

flag = myData.ReadOneRecord(currentRecord - 1);

if (myData.Status == 0)

{

ClearTextboxes();

txtRecord.Text = currentRecord.ToString() + “ Not Active”;

}

else

{

if (flag == 1)

{

ShowOneRecord();

txtRecord.Text = currentRecord.ToString();

}

else

{

MessageBox.Show(”Record not available.”, “Error

Read”);

flag = 0;

}

}

}

catch

{

flag = 0;

}

myData.Close();

return flag;

}

This solution displays the record position (currentRecord) in the txtRecord object but appends a Not Active message after clearing out all the textboxes. Presenting the information this way doesn't leave the user in the dark when an inactive record is read.

You could also put Read Active - Read All radio buttons on the form to give the user more control over reading the data.

Exercise 2 Solution

Obviously you can, or I wouldn't have asked the question. In the getRecordCount() method from Listing 13-6, delete the line,

records = myFile.Seek(0, SeekOrigin.End); // Position file pointer

and replace it with:

FileInfo myInfo = new FileInfo(fileName);

records = myInfo.Length;

This exercise simply confirms that there are almost always multiple ways to accomplish a given task. Part of the journey to becoming an experienced programmer is to learn what these alternatives are and which one offers the best solution for the task at hand.

Exercise 3 Solution

After people learn how to use random access files, they tend to throw away sequential files. You should never throw away a programming tool. Sequential files are often used for persisting nontransactions-based information. Nontransactions-based refers to those sets of data that do not require updating and editing. The density of sequential files makes them perfect correspondence types of data storage. However, when the data likely needs to be updated or otherwise edited, random access files are usually a better choice.

Exercise 4 Solution

This is sort of a trick question because there is no “right” answer. Without much conscious effort, I tend to use serialization techniques to pass an object's state to other applications that need it for their processing tasks. As such, it seems that I write one object to the file, pass it on to some other app, and that app hands it back to me with additional (or altered) information about the object's state. Perhaps it's just me and the type of programming I do, but most of the time I use serialization for temporary storage.

If I know that the object's information is going to hang around on a fairly permanent basis, like a client file, I tend to use standard read/write code. Therefore, the correct answer is, “It depends….”

Exercise 5 Solution

An About box is normally found under the rightmost menu option of MDI applications. Convention has Help as the rightmost menu option in MDI applications. Normally, the Help topic offers a means by which you can provide information about how to use the program. The last submenu item under Help is About. Therefore, the first thing you probably need to implement an About box is an application that implements the MDI interface.

Next, you need to create a new menu option, perhaps named mnuAbout. Finally, you need to add a new form to the project (frmAbout) and add labels that convey whatever information you deem desirable by the user. Finally, add the code,

private void mnuAbout_Click(object sender, EventArgs e)

{

frmAbout frm = new frmAbout();

frm.ShowDialog();

}

and recompile the program.

Chapter 14

Exercise 1 Solution

The two commands are

SELECT COUNT * FROM Friends WHERE status = 1

and

SELECT SUM(status) FROM Friends

The reason the aggregate SUM function works is because you assigned the value of 1 to an active member and 0 to inactive members. Therefore, SUM also produces a count of active members. This second form is less useful because it relies on the value of status.

Exercise 2 Solution

The SELECT statement might be written as:

SELECT * FROM Friends WHERE status = 1 AND ADDR2 <> ‘’ AND State = ‘IN'

Note that the middle expression of the WHERE predicate is two single-quotation marks, not a double-quotation mark.

Exercise 3 Solution

There are several ways to do this. First, you could create a new static class (for example, clsGlobalFriendData) and give it property members that match the data in the Friends data structure and use the property get and set methods to make the data available to clsDB. This would work because a static data item is instantiated at load time, so there is always one (and only one) instance of the class. You could then copy the data from frmAddFriend into the static class object and then let clsDB retrieve it. Although this works, it means that you always have a copy of that object hanging around chewing up resources when you may not need it. Also, there's just something about that approach that seems “messy”…sorta like using an H-bomb to kill an ant.

A less H-bombish approach would be to copy the contents of each textbox object into a string array and pass that string array to a new InsertFriend() method. A second parallel array would need to keep track of whether the string variable represented a string or numeric value. Assuming strholds the data and type holds the data type, you might have something in the InsertFriend() method that looks like:

public string InsertData(string[] str, int[] type, string sqlCommand)

{

int i;

for (i = 0; i < str.Length; i++)

{

switch (type[i])

{

case 0: // Numeric data

sqlCommand += str[i] + “,”;

break;

case 1: // String data

sqlCommand += “'” + str[i] + “',”;

break;

default:

break;

}

}

i = sqlCommand.LastIndexOf(','); // Strip off last comma

sqlCommand = sqlCommand.Substring(0, i) + “)”;

return str;

}

This assumes that the first part of the INSERT command has been built and only the textbox information needs to be added to the sqlCommand string.

Exercise 4 Solution

This is a short question with a fairly long answer. The necessary code is similar to the code found in frmQuery, which currently is:

private void DoQuery()

{

try

{

ds = new DataSet(); // Instantiate DataSet object

conn.Open();

command.CommandText = txtQuery.Text;

adapter = new OleDbDataAdapter(command);

adapter.Fill(ds);

dataGridView1.DataSource = ds.Tables[0];

command.ExecuteNonQuery();

}

catch (Exception ex)

{

MessageBox.Show(”Error: “ + ex.Message);

}

finally

{

conn.Close();

}

}

To modify the SQL query to use LINQ instead, change the code to something like

private void DoQuery()

{

string buff;

try

{

ds = new DataSet(); // Instantiate DataSet object

conn.Open();

command.CommandText = txtQuery.Text;

adapter = new OleDbDataAdapter(command);

adapter.Fill(ds);

dataGridView1.DataSource = ds.Tables[0];

// New stuff starts here...

DataTable buddies = ds.Tables[0];

IEnumerable<DataRow> query =

from buddy in buddies.AsEnumerable()

select buddy;

// Sample output...

foreach (DataRow myRow in query)

{

buff = myRow.Field<string>(”LastName”) + “ “ +

myRow.Field<string>(”Zip”);

}

}

catch (Exception ex)

{

MessageBox.Show(”Error: “ + ex.Message);

}

finally

{

conn.Close();

}

}

The only major change is the inclusion of the System.Collections.Generic namespace. You must do this because in this example you did not use the var data type, but instead chose to use the IEnumerable interface using a DataRow object for the generic. The string variable buff then simply picks off two of the fields that result from the dataset using the Friends table.

Chapter 15

Exercise 1 Solution

To modify the SQL query to use LINQ instead, change the code to something like this:

private void DoQuery()

{

string buff;

try

{

ds = new DataSet(); // Instantiate DataSet object

conn.Open();

command.CommandText = txtQuery.Text;

adapter = new OleDbDataAdapter(command);

adapter.Fill(ds);

dataGridView1.DataSource = ds.Tables[0];

// New stuff starts here...

DataTable buddies = ds.Tables[0];

IEnumerable<DataRow> query =

from buddy in buddies.AsEnumerable()

select buddy;

// Sample output...

foreach (DataRow myRow in query)

{

buff = myRow.Field<string>(”LastName”) + “ “ +

myRow.Field<string>(”Zip”);

}

}

catch (Exception ex)

{

MessageBox.Show(”Error: “ + ex.Message);

}

finally

{

conn.Close();

}

}

Exercise 2 Solution

var query = from p in numbers // The “Query”

where p > lo && p < hi

orderby p

select p;

Exercise 3 Solution

Perhaps the simplest answer is that LINQ enables you to query across all object types whereas SQL is limited to databases. Therefore, programmers familiar with SQL can apply a good part of that knowledge directly to a LINQ implementation. Although there are ways to shoe-horn SQL queries to “fit” all object types, it would normally involve a lot of explicit casts to make things work. LINQ gives you the ability to work with various object types, yet retain strong typing of the data that comes from a query.

As you saw earlier in the chapter, var types that are often used with LINQ are not objects, but assume the type of the data used to instantiate the var variable.

Chapter 16

Exercise 1 Solution

Clearly this is a problem that begs for an inherited solution. The base class would be called clsMembers and would contain all the information that pertains to all members. That data would include name and mailing address, billing information, and the like. The derived classes would be clsSenior,clsRegular, clsJunior, and clsSocial. Each of these classes would have information that pertains to that class only (for example, dues rate, voting privileges, monthly food minimums, and so on.)

Exercise 2 Solution

You can tell that this line of code appears in the clsJunior class and is part of the constructor for the class. You can also tell that the base class has a similar constructor that the writer wants to call as part of setting the initial state of the object. As a general rule, the sequence displayed here is good because it shows that the writer is consistent with the initialization of new objects.

Exercise 3 Solution

Their concern is that the derived classes can directly change the value of a data item in the base class without having to be subject to any validation code that might be associated with that property's set method in the base class. This is a real concern. However, the concern, although real, is not serious. Users of the class will still go through the property's set method, and only the programmers of the derived classes can abuse this concern. If you can assume that your programming colleagues aren't out to sabotage your project, it's probably not going to be a serious problem.

Exercise 4 Solution

If you define a class using the sealed keyword, you are informing the compiler that it should not let this class be used as a base class for further inheritance. For example,

public sealed clsMyClass

prevents anyone from using clsMyClass as a base class for further inheritance.

Exercise 5 Solution

Most of the abilities of base-derived class behavior can be written without using inheritance. However, you do end up duplicating a lot of code by not using inheritance. Also, polymorphic behavior is not possible without using inheritance.

Chapter 17

Exercise 1 Solution

Students forget to use try-catch blocks in their code. This is especially bad for printers because they are largely electro-mechanical devices that have a lot of components that can fail. Also, running out of ink or paper is also a common issue.

Exercise 2 Solution

Threading is the ability to have more than one execution thread within the same process running in an application.

Exercise 3 Solution

It is not unusual for one process to take a fairly long period of time, such as printing. Without threading, the user is forced to sit there and wait for the process to finish. With threading, the user can do other activities while the threaded process completes its task.

Exercise 4 Solution

A BackgroundWorker is an object that enables you to easily start, control, and end a thread. BackgroundWorker objects make threading about as easy as it's going to get.

Exercise 5 Solution

Yes. There are synchronization issues to deal with, conditions that can lock up the system if done improperly (for example, deadlocks), plus other problems that can arise (that is, race conditions). These problems become more serious when the main thread is working with databases.

Exercise 6 Solution

Refactoring in Visual Studio is simply an easy way to implement changes to an existing body of code. Removing duplicate code (method extraction) and improving encapsulation are two of the most common uses of refactoring a program.

Chapter 18

Exercise 1 Solution

A web page is an HTML file capable of being rendered into a visual representation by a browser.

Exercise 2 Solution

Static web pages have HTML content that does not vary, whereas dynamic pages may vary their content in response to user (or other) inputs.

Exercise 3 Solution

Unlike pure HTML, ASP.NET enables special tags in the file that can be processed by the ASP engine for further processing. The instructions for this processing are located in the code-behind file. After the processing finishes, the results are then formatted as an HTML file and sent back to the client for rendering.

Exercise 4 Solution

Because of the statement,

interestRate /= 1200;

if the user does not enter a value for the interest rate (or an interest rate of zero), the program crashes.

Exercise 5 Solution

There are numerous ways to address the issue. First, it would make sense to surround all the statements in the btnSubmit_Click() event with a try-catch block. This would at least provide a means to prevent a program crash. Also, the content of each textbox could be examined if the TryParse()fails rather than just setting the payment to zero. Another possibility is to set the txtPayment .Text property to “interest rate error” and return from the TryParse() statement block. If you code in a commercial environment, there may be a company policy for such things. Otherwise, pick a method that prevents the crash yet still tells the user what went wrong.