2. Types, Methods and Statements Lab

 

Overview:  you'll practice working with .NET and the C# language by creating a console-based application in Visual Studio .NET.  You'll also experiment with some of the more subtle aspects of OOP, namely testing for equality and sorting.


To earn a passing grade for this lab, you must demonstrate both working programs to the instructor no later than June 1.


Part 1:  Visual Studio .NET Project Setup

1.       Startup Visual Studio .NET: 

·         Start menu

·         All Programs

·         Microsoft Visual Studio .NET 2003

·         Microsoft Visual Studio .NET 2003

If this is the first time, you may be presented with a start page asking you to select your profile (VB programmer, C# programmer, etc.).  Accept the default settings by simply switching to the Projects tab (if available).

·         Click File | New | Project (or click the New Project button)

2.       At this point you should be presented with the New Project dialog.  Select "Visual C# Projects" as your Project Type, scroll down to select "Console Application" as your Template using a single click.  Set the Location by browsing to/creating a new directory for this lab exercise on the H: drive. Then set the Name to something creative like "LanguageApp".  You may see this message:

If so, simply click OK and continue.

3.       Visual Studio will create a main class called "Class1.cs" with an empty static main method.  Change the name of the class to MainClass, and change the name of the file to "MainClass.cs" (right-click on the icon for "Class1.cs" in the Solution Explorer (top-right) window of Visual Studio, and rename to "MainClass.cs").  Notice the comments automatically inserted by Visual Studio.  These comments have a special format that allows a tool to generate web-based documentation. 

4.       Let's get comfortable with using Visual Studio .NET.  Modify the main method to simply output "Hello there!" to the screen --- wow, isn't IntelliSense wonderful!  You accept IntelliSense's suggestion by pressing the TAB or ENTER key; you dismiss IntelliSense by pressing ESC.

5.       To compile a program, use the Build menu and select the Rebuild option for best results.  To run, press F5.  Go ahead and press F5.  The program will run, a command window will open, your message will flash on the screen, and then the command window will close since the program has ended.  Normally we want to keep the command window open so we have time to actually see the output :-)  A common trick is to wait for the user to enter something on the keyboard, i.e., a call to System.Console.ReadLine().  Add this to the end of your main subroutine and run again; now the command window remains open until you press ENTER

6.       While we're getting setup with Visual Studio, now is a good time to make the fonts bigger so it's easier for you to work with VS.  Under the Tools menu, select Options, and then under the list for Environment, select Fonts and Colors.  Show the Settings for Text Editor (this should be the default that appears), and set the font to say 12pt.  Then show the settings for the Text Output Tool Windows, and set that to 12pt.  Finally, in the list that appears in the left pane, select Text Editor, then select All Languages, then select Tabs.  Set the Tab and Indent Size to 4, and select the "Keep tabs" option.  Click OK.  Visual Studio is now reasonably configured.

7.       Okay, you're ready to start programming!


Part 2:  Classes, Arrays and Namespaces

1.       Above is a screenshot of the application you will build.  The idea is to input some employee information from the user, collect them in an array of Employee objects, and then manipulate and output the data.  Note that the application is dynamic, in that the user determines how many employees will be input --- 3 in the example above --- and the application reacts accordingly.  We'll build the application slowly, step by step...

2.       The first step is to add an Employee class to your project: 

·         Click Project

·         Select Add Class

·         Set file name to "Employee.cs". 

An employee has 4 fields:  first name, last name, salary and email address.  The names and email address are strings; the salary is a decimal.  Each of these attributes should be private. 

Implement a parameterized constructor with four arguments to set up the initial values of the employee’s fields.

Override ToString() to return the employee's full name in the format “last name, first name”.  Don’t forget the keyword “override”.

3.       Back in the Main() method of MainClass, declare a variable emp which will refer to an array of Employees of unknown size.

4.       In the Main() method, prompt for and read the number of employees you plan to enter.  Convert the number of employees to an integer.

5.       Write a private method called GetEmployees(int N) that returns an array of N employee objects:

 

private static Employee[] GetEmployees (int N)   {

            System.Diagnostics.Debug.Assert(N > 0);

  .

  .

  .

}

 

The method should create an Employee array of size N

Prompt the user for information for each of N employees.  Read the data for each employee from the user at the keyboard and instantiate a new Employee. 

Place each Employee object into the array, then return a reference to the array object back to the caller.   

When inputting the salary, note that .NET only allows you to input either a character, or the entire line (as a string).  The best approach in this case is to input the entire line using System.Console.ReadLine(), and then type convert the input string to an decimal.  Use Intellisense to figure out which method in the Convert class should be used to do this.

6.       Back in the Main method, call GetEmployees() to input the employee data and return an array of employee objects.  Save the returned array reference in the local variable, emp,  of type Employee[].

7.       Immediately loop through the array and output each employee to the screen.  Use the foreach loop to iterate through the array, calling ToString() on each employee object and outputting the result.

8.       Compile, run and test.  At this point you should be able to run, input some employees, and print them back out --- as shown in the earlier screenshot.  Obviously, keep N small :-)

9.       Notice that Visual Studio automatically imports the System namespace by putting using System at the top of each source file.  This means you can drop the System prefix if you prefer and write Console.WriteLine(...) and Console.ReadLine().  Try it if you haven't already.

On the other hand, note that your Employee class is declared within a namespace (LanguageApp).  This implies that unless you import the namespace, you must refer to the Employee class as LanguageApp.Employee.  But you probably referred to the class as Employee.  So why did it work?

Simple:  The main class is part of the same namespace, so it's perfectly legal for the main class methods to refer to the class simply as Employee.  But go ahead and try the fully-qualified name LanguageApp.Employee -- that should work as well.

10.   DEMONSTRATE THIS SOLUTION NOW OR SAVE A COPY OF THIS VERSION AND DEMONSTRATE IT LATER. 

11.   Good work!


Part 3:  Searching

1.       The next step is to let the user search for an employee in the array, and if found, output that employee's salary.

2.       Add a GetSalary() method to the Employee class that returns the employee's salary as a nicely-formatted string like this: 

public string GetSalary() {

return this.salary.ToString("$#,##0.00");

}

3.       Modify the Main method so that after outputting the employees to the screen, you input an employee's first and last name from the user.  The modern, object-oriented way to compare Employees is to compare the objects themselves, not the data fields.  Modify the Main method so that after inputting the employee's first and last name from the user, you create a Employee object theE with a dummy salary and email address.  Write a loop to iterate through the employees array, and for each employee object in the array use the = = operator to compare the employee in the array to employee theE using the = = operator.  If a match is found output the employee's salary; if no match is found, inform the user that the search failed.

4.       Run and test.  To make testing easier, feel free to hard-code the value of N in main, and hard-code the GetEmployees method to return an array of predetermined Employee objects.  Hint:  A decimal type literal ends with ‘m’.  For example:  100.92m

5.       Using this approach, no matter what the user inputs, the program will never find a match.  As discussed in class, this is because the = = operator compares object references, not object data. 

6.       Okay, being good OO programmers, rewrite the search loop so that for each employee in the array, you call Equals(theE) to compare the objects properly.  Run and test, and once again, no matter what the user inputs, the program will never find a match.

7.       The problem is that the Employee class does not fulfill its half of the responsibility by overriding the Equals method.  Modify the Employee class to override Equals, where two employees are equal if they have the same first and last names.  Now compile, run and test.  The search should now work properly.

8.       One last thing:  if you look VERY carefully at the output messages when you compiled the program (you'll have to scroll the output window), you'll notice a warning saying that you didn't override GetHashCode().  Likewise, you'll see a squiggly under the class name Employee.  While you are free to ignore the warning, I don't recommend it   So let's properly override GetHashCode() as well.

9.       Here's the rule for implementing GetHashCode():  if obj1.Equals(obj2), then obj1.GetHashCode() == obj2.GetHashCode().  In other words, if two objects are equal by your definition of Equals, then those same objects must hash to the same integer code.  If the two objects are different by your definition of Equals, then it doesn't matter what GetHashCode returns (though the hope is that you'll return different hash codes). 

10.   Okay, so go ahead and override GetHashCode() in the Employee class.  Since two employees are equal if their names are equal, a reasonable hashing scheme that adheres to the rule is one in which we add together the individual hash codes for the first and last names.  As a result, if two objects have the same first and last names, then those objects will also have the same hash code.  Compile, run and test.  You should notice no difference, other than the warning is now gone.  But even though you don't notice a difference today, forgetting to override GetHashCode() will cause a problem someday...

11.   DEMONSTRATE THIS SOLUTION NOW OR SAVE A COPY OF THIS VERSION AND DEMONSTRATE IT LATER.  Excellent!


Part 4:  If time permits and you want to experiment further...

A.  Sorting, Min, Max and Median (OPTIONAL)

1.       The goal is to output the min, max and median salary values for the employees input by the user.

2.       Given a sorted sequence of numeric values, the median is the value in the middle.  If the sequence consists of an odd number of values, the median is literally the middle value.  If the sequence consists of an even number of values, the median is the average of the two middle values.  Either way, we need to sort the values first...

3.       The good news is that .NET contains a sort routine that you can simply call, and it will do the work of sorting the values for you.  The subroutine is named Sort, and it is a static method residing in the System.Array class.  Pass your array to this subroutine, and it will come back sorted.  The good news is that System.Array.Sort(array) can sort an array of anything.  The bad news is that the objects in the array must implement a particular .NET interface (i.e., a set of methods) in order for Sort to work.  Since our Employee class does not implement the required set of methods, we cannot sort our employees array just yet.  However, the System.Int32 class does implement the required set of methods, so the solution is to create a separate array of type int[], copy the employee salaries over into that array (converting to type int), and sort.

4.       Go ahead and modify your main method so that after it performs the search, it sorts the salaries via a separate int[] array and then for now outputs the min and max salaries.  Run and test.

5.       Let's compute and output the median.  Assume for a moment that the user will enter an odd number of employees.  In this case the median is easy:  it's the middle element of the sorted sequence.  Given an array with N elements, the index of the middle element is typically computed by dividing N by 2 and storing the result in an integer variable.  However, you always have to consider the question of rounding.  Dividing an odd number by 2 yields a .5 --- does C# round-up, round-down, or both (i.e., alternating)?  We need C# to round-down, for example 5 / 2 needs to be 2 (2 is the middle of an array with 5 elements 0 .. 4), and 7 / 2 needs to be 3 (3 is the middle of an array with 7 elements 0 .. 6), and so on.  You could write some test code to see what C# does, or you could just make sure it works properly by using the FxCL.. 

6.       In mathematical terms, we want the floor of N / 2, i.e., "the largest whole number less than or equal to the result."  Thus, to properly compute the middle index and guarantee that we round-down, we can use the System.Math.Floor(result) method as follows:

int  mid;

mid = (int) System.Math.Floor(N / 2.0);

 

We divide by 2.0 to force real division, then we ask the FxCL to round-down for us.  Based on this value of mid, you can now access and output the median final salary (we are still assuming N is odd).  Compile, run and test with odd values for N.  Convince yourself that you are outputting the correct median value.

7.       You may be wondering:  Do we really need to use System.Math.Floor?   Turns out you don't need it here in C#, but you do in VB.  The underlying point here is that you need to program defensively if you don't already.  Switching between languages is so common these days that it's dangerous to rely upon language-specific details:  rounding, operator precedence, short-circuiting, etc.  Instead, you need to focus on the concepts, and develop general, language-independent techniques for implementing those concepts. 

8.       The last step is to handle the case where N is even.  In this case, the median is the average of the middle two elements.  For example, given an array with N = 8 elements (0 .. 7), then mid = 4, and thus you want to take the average of elements #3 and #4.  That's easy enough, as long as you remember to divide by 2.0 to force real division, and store the result in a variable of type double.  The only tricky part is determining whether N is even or odd.  A common trick is to use the "mod" or "remainder" operator, which in C# is %.  The result of N%K is the integer remainder of dividing N by K.  Thus, if N%2 is 0 then N is even, else N is odd.  Here's the code:

if (N % 2 == 0)  // no remainder means N is even!

            System.Console.WriteLine("N is even!");

else

            System.Console.WriteLine("N is odd!");

 

Using this code, compute and output the median value properly whether N is even or odd.


B.  Formatting, Checking Inputs, Implementing an Interface (OPTIONAL)

1.       When outputting real numbers (e.g., doubles) to the screen, by default .NET will not print the 0's to the right of the decimal point.  For example, the real number 12.0 is output as 12, not 12.0.  Formatting is controlled via the ToString() function by supplying a format string.  In the case of real numbers, if you convert the variable to a string using the format string "0.00", then you're telling .NET to always print at least 1 digit to the left of the decimal point, and 2 digits to the right --- whether those digits are 0 or not.  Here's an example:

double  median;

string  s;

median = ...

s = median.ToString("0.00");

 

Modify your program to output the median using a format string like the one above, and test.

2.       What if the user enters 0 for the number of employees?  Or a negative value?  What does your program do?  Handle this situation by either stopping the program at this point (by returning from the main method or calling System.Environment.Exit(1)), or by repeatedly inputting an integer from the user until he/she enters a value > 0.

3.       The System.Array class contains an IndexOf(Array a, object obj) method for searching a given array for a given object.  This generic, linear search method operates by comparing obj to each object in array a; comparison is performed by calling obj's Equals() method.  If obj is found then its index in the array is returned, otherwise a negative number is returned.  Extend your main program to also search based on this IndexOf() method, and confirm that IndexOf() reports the same findings as your search loop.

4.       It would be more efficient, and more useful, if we could sort the array of employees directly.  This would save us from having to create a second array, and from having to copy all the values over to the second array.  Also, as it stands right now, there's no way to associate the sorted salaries with the employees.  In particular, there's no easy way to determine the employee with the max salary, the min salary, or the median.  The solution is to make the Employee objects sortable by implementing the method(s) defined in the .NET interface IComparable.  The employees should order themselves based on their salary.  Note that we haven't talked about interfaces yet, so if interfaces are new to you, you might want to wait on this one :-)

5.       Okay, that's plenty.  Take a well-deserved break!