Showing posts with label Tutorial. Show all posts
Showing posts with label Tutorial. Show all posts

Sunday, February 8, 2004

Tutorial 3 - Types and parameter passing primer

This primer explains three different concepts.

  • Value types and reference types
  • Passing by value and passing by reference
  • Garbage collection (brief overview)

Background

I recommend the following sites for more thorough explanations (especially Jon Skeet's two pages) or just another viewpoint.

Value Types and Reference Types

Every object in C# (and the .NET environment) is one of two types: Value or Reference. I'll pull an ASCII art from Chris Brumme's BLOG.

              System.Object
                 /       \
                /         \
         most classes   System.ValueType
                            /       \
                           /         \
                  most value types   System.Enum
                                       \
                                        \
                                       all enums

Value Types

The section "most value types" in the above picture includes most primitives like int, float, and double. Structs (see the MSDN struct tutorial) are also value types. A struct is a "light-weight" class. It is declared with syntax very similar to that for declaring a class. But there are some differences in the way it is used since it is a value type. One struct that is commonly used, but sometimes overlooked as a value type, is DateTime. When a value type is created, a spot in memory is reserved for it and the contents are placed there.

MSDN Link: list of value types

The following commands declare and assign a variable x. In line 1, the variable x is declared as an int, which is a value type. This creates a spot in memory for the variable x sized to hold an int. In line 2, the value 37 in placed in that spot. Thus we say x has a value of 37.

1          int x;
2          x = 37;
The same two lines can be expressed as:
    int x = 37;

Some value types use the new keyword to call a constructor for setting an initial value. The following command creates a spot in memory to hold a DataTime struct, and sets the value of that spot in memory to 2000-01-01.

    DateTime dt = new DateTime(2000, 1, 1);  // Jan 1, 2000

Reference Types

The section "most classes" in the above picture includes the reference types. This includes all user defined classes (like our Airplane class from before). It also includes the string class.

Creating a reference type object requires the new keyword. The following command declares a variable airplane1, which holds a reference to an Airplane object on the heap. The Airplane object on the heap contains the data such as "Boeing" and "747". The variable airplane1 is similar to a C++ pointer in that all it really holds is an address (or reference).

    Airplane airplane1 = new Airplane ("Boeing", "747");

The following line of code does not actually create a new Airplane object. It simply creates a variable (airplane1) that could hold a reference to an Airplane object.

    Airplane airplane1; // no object created

Differences between value and reference types

There are a few key differences between value types and reference types.

  • Reference types are always stored on the heap. Value types can be stored on the stack (for local variables) or on the heap (generally for non-local variables, like static variables and member variables of a type that is already on the heap). See Memory in .NET - What goes where? by Jon Skeet for a more thorough explanation.
  • The value of a reference type variable can be null. That is, a reference type variable doesn't have to point to a valid object. Value type variables, on the other hand, cannot have a null value.

Analogy for value and reference types

I like to use the following analogy to understand value types and reference types. (I presented this earlier in Classes, objects and methods).

Reference types are like balloons and value types are like balls. A variable is like your hand. When you hold a balloon, you use a string to hold onto it. This string is a reference to the balloon. This is similar to how a variable holds a reference to a reference type object. When you hold a ball, you hold the actual ball in your hand, no strings. This is similar to how a variable holds a value type object. It holds the actual object (or value), no references and no strings attached.

We will continue this analogy later.

Passing by Value and Passing by Reference

This is an important concept in C# and .NET. I highly recommend either of the two pages listed above on Parameter passing.

There are two ways of passing variables to a method:

By value
Passes the value that a variable holds. Think of it as passing a copy of a variable.
By reference
Passes a reference to a variable. Think of it as passing the actual variable itself, as opposed to simply whatever value it contains.

The default is to pass by value. The keywords ref or out are required before a parameter to pass by reference.

The above statements always hold for all variables: value type and reference type. You either pass the variable's value or a reference to the variable. There is a lot of confusion due to the similarity in terms value type, reference type and passing by value and passing by reference. To alleviate this confusion, we'll look at each of the following four combinations:

  1. Passing value types by value
  2. Passing value types by reference
  3. Passing reference types by value
  4. Passing reference types by reference

Passing value types by value

FunctionA (below) simply increments a number by 10. I've declared it static which means that it is not part of a specific object, but rather part of the class itself (even though the class isn't shown). This has no bearing, however, on the principles demonstrated below.

public static void FunctionA (int x)
{
    x += 10;
}

public static void Main () 
{
    int myInt = 37;
    Console.WriteLine ("Before calling function, myInt = " + myInt);
    FunctionA (myInt);
    Console.WriteLine ("After calling function, myInt = " + myInt);
    Console.ReadLine();
}

In this example, Main creates an int (myInt) and prints the value to the screen. It then passes myInt, by value, to FunctionA. This means that FunctionA gets a new copy of the value of myInt. When FunctionA increments x by 10, it does not change the value of myInt since it is changing its own copy. Thus, the result is as follows:

Before calling function, myInt = 37
After calling function, myInt = 37

Analogy

To continue the balloon / ball example... Calling a function is like handing a balloon or ball over to another friend. In this example, we are talking about value types, which are analogous to balls Passing by value doesn't have a perfect analogy since it is like passing a copy. Thus, it would be like passing a copy of the ball that is in your hand over to a friend. You keep your original ball and your friend can't touch it.

Passing value types by reference

FunctionB (below) also increments a number by 10. However, by using the keyword ref when declaring the parameter int x, FunctionB indicates that the int must be passed by reference. The C# compiler requires that we also use the ref keyword when calling FunctionB so that it is obvious that we know what we are doing. We'll see why in a second.

public static void FunctionB (ref int x)
{
    x += 10;
}

public static void Main () 
{
    int myInt = 37;
    Console.WriteLine ("Before calling function, myInt = " + myInt);
    FunctionB (ref myInt);
    Console.WriteLine ("After calling function, myInt = " + myInt);
    Console.ReadLine();
}

In this example, Main creates an int (myInt) and prints the value to the screen. It then passes myInt, by reference, to FunctionB. This means that FunctionB gets a reference to myInt, not a new copy myInt's value. When FunctionB increments x by 10, it also changes the value of myInt (since x more or less points to myInt). Thus, the result is as follows:

Before calling function, myInt = 37
After calling function, myInt = 47

Using ref allows a function to change the contents of a variable passed in, which can lead to tricky-to-debug systems and spaghetti-code. The C# compiler requires that ref be used on both the declaration of FunctionB and the call of FunctionB (within Main) so that it is immediately obvious that variables passed into FunctionB may not have the same value when FunctionB returns.

Analogy

To continue the balloon / ball example: In this example, we are still talking about value types, which are analogous to balls. Passing by reference is like passing your ball over to your friend. Then when your friend is finished (the function returns) you get it back, along with any changes he made to it.

Passing reference types by value

FunctionC (below) uses the reference type StringBuilder. I chose StringBuilder because it is a simple, easy to use class that meets the requirements of being a reference type. Any other class would behave the same here.

//Note, StringBuilder is part of System.Text namespace.
public static void FunctionC (StringBuilder x)
{
    x = new StringBuilder ("Hello Universe");
}

public static void Main () 
{
    StringBuilder sb = new StringBuilder ("Hello World");
    Console.WriteLine ("Before calling function, sb = " + sb);
    FunctionC (sb);
    Console.WriteLine ("After calling function, sb = " + sb);
    Console.ReadLine();
}

In this example, Main creates a new StringBuilder instance with an initial value of "Hello World" and assigns it to the variable sb. Thus, sb holds a reference to our new StringBuilder object. FunctionC is called and sb is passed by value. This means that the reference to the StringBuilder object is being passed by value. FunctionC gets the value of the reference (or in other words, it gets a new copy of this reference). At the start of FunctionC, printing x would print "Hello World". The variable x is assigned to a new StringBuilder object with a value of "Hello Universe". However, this does not affect sb. After calling the function, sb still prints "Hello World".

Results

Before calling function, sb = Hello World
After calling function, sb = Hello World

Analogy

To continue the balloon / ball example: In this example, we are now talking about reference types, which are analogous to balloons. Remember that you only hold a string to a balloon, not the actual balloon. Passing by value is like getting a new string, connecting it to your balloon, and then giving this new string to your friend. If your friend cuts the string or connects it to a different balloon (as is done in the above example), it has no affect on your string.

Passing reference types by reference

FunctionD (below) also uses the reference type StringBuilder.

//Note, StringBuilder is part of System.Text namespace.
public static void FunctionD (ref StringBuilder x)
{
    x = new StringBuilder ("Hello Universe");
}

public static void Main () 
{
    StringBuilder sb = new StringBuilder ("Hello World");
    Console.WriteLine ("Before calling function, sb = " + sb);
    FunctionD (ref sb);
    Console.WriteLine ("After calling function, sb = " + sb);
    Console.ReadLine();
}

Again in this example, Main creates a new StringBuilder instance with an initial value of "Hello World" and assigns it to the variable sb. Thus, sb holds a reference to our new StringBuilder object. FunctionC is called and sb is passed by reference. This means that the reference to the StringBuilder object is being passed by reference. The variable x will hold the same reference as sb holds in Main. When the variable x is assigned to a new StringBuilder object with a value of "Hello Universe", it also affects sb. After calling the function, sb prints "Hello Universe".

Results:

Before calling function, sb = Hello World
After calling function, sb = Hello Universe

Analogy

To continue the balloon / ball example: In this example, we are talking about reference types, which are analogous to balloons. Remember that you only hold a string to a balloon, not the actual balloon. Passing by reference is like handing the string in your hand over to your friend. When your friend is finished (i.e. at the end of the function) he hands it back to you. If your friend cuts the string or connects it to a different balloon (as is done in the above example), then you have a new balloon at the end of your string. What a nice surprise!

Parameter Passing Summary

We've discussed passing by value and passing by reference for both value types and reference types. I should point out that when dealing with reference types, it doesn't matter how you pass the object, the calling function will always be able to make changes to the object itself. In the following example, we are passing by value, however sb and x both point to the same object, so x.Append() will affect sb.

//Note, StringBuilder is part of System.Text namespace.
public static void FunctionE (StringBuilder x)
{
    x.Append(" from me!");
}

public static void Main () 
{
    StringBuilder sb = new StringBuilder ("Hello World");
    Console.WriteLine ("Before calling function, sb = " + sb);
    FunctionE (sb);
    Console.WriteLine ("After calling function, sb = " + sb);
     Console.ReadLine();
}

Results:

Before calling function, sb = Hello World
After calling function, sb = Hello World from me!

Garbage Collection

To summarize, I'll use the words of Jeffrey Richter:

The garbage collector checks to see if there are any objects in the heap that are no longer being used by the application. If such objects exist, then the memory used by these objects can be reclaimed

Analogy

When you let go of a string, the attached balloon floats away. Even if two balloons are tied together, they will both float away if they are not tied down. The garbage collector is a small plane that goes around collecting these balloons. Note, the plane doesn't have any set schedule or order for collecting balloons, it just happens as needed. (Ok, there are some rules about when it can happen, but not when it will happen).

When the garbage collector needs resources, it will find all of the objects that are not "tied down" and collect them. There are a few more advanced topics when it comes to Finalizers and the IDisposable interface. We'll get into that later.

Tutorial 2 - Access modifiers and properties primer

This builds off the last primer, Classes, objects and methods. This will quickly introduce you to access modifiers and properties.

Modifiers

There are several types of modifiers. In general, they are used to indicate how other classes may use the elements (variables, types, methods) of your class.

Access modifiers allow some parts of a class to be public (that is, usable by the rest of the world) and other parts to be private implementation details (hidden to the rest of the world). This helps provide encapsulation, or the ability to hide details of implementation away from other classes that should not know the inner workings.

The modifiers used in the Airplane example below include: public, private, const, readonly, and override

  • Members declared as public can be used outside the class.
  • Members declared private can only be used inside the class. Private is the default accessibility for members of a class.
  • Fields (or variables) declared as const are constants set at compile time.
  • Fields (or variables) declared as readonly can only be set during object initialization (construction) but never set or changed after that.
  • Override has to do with class inheritance, which is a later topic. It is used on the ToString() method since all objects have a method ToString() and we want to override, or change, the behavior of the default implementation for Airplane objects.

First, we add a public readonly field for MaxAltitude. This will be set when the airplane is constructed (presumably based upon the model of the aircraft).

       public readonly int MaxAltitude;

Next, we add a public const field for MaxSpeed. In this example we will assume all airplanes can travel at the same max speed (not very realistic, I'd like to see a Cessna trying to keep up with a F-15 Eagle).

       public const int MaxSpeed = 700;

All of our other fields will be set private, so that other classes do not try to do something stupid like make our airplane fly too fast or too high. This is a good time to introduce properties.

Properties

Properties allow the getting and setting of internal values (often private fields) through special get and set methods.

MSDN Link: Properties

For the airplane class we have a few private fields: direction, altitude and speed. By convention, private variables generally start with a lowercase letter. Sometimes people use _ or m_ as a prefix, but I prefer not to clutter code with prefixes.

The public properties Direction, Altitude, and Speed will allow others to get and set these private field. By convention, public members are capitalized. These properties will include two methods: get and set. The get method is used to get the value and set is used to set a new value. We implement error checking in the set methods to guarantee the integrity of our data (and our airplane, we don't want it going to fast!).

We also make readonly properties for manufacturer and model since these cannot be changed on an airplane once created. A readonly property does NOT use the readonly keyword. Instead, it implements just a get method with no corresponding set method.

Let's take a look at the new airplane class.

using System;

namespace TestAirplane
{
   public class Airplane
   {
       private string manufacturer;
       private string model;
       private float direction;
       private int altitude;
       private int speed;

       /// <summary>
       /// The Maximum Altitude that this plane can fly in ft.
       /// This is a readonly value that is set in the constructor,
       /// and cannot be changed by any other code.
       /// </summary>
       public readonly int MaxAltitude;

       /// <summary>
       /// The Maximum Speed that this plane can fly in mph.
       /// This is a constant that can not be changed by any other code.
       /// (All planes have a MaxSpeed of 700 mph, in this example.)
       /// </summary>
       public const int MaxSpeed = 700;

       /// <summary>
       /// Read-only property to get the manufacturer
       /// </summary>
       public string Manufacturer
       {
           get { return this.manufacturer; }
       }
      
       /// <summary>
       /// Read-only property to get the model
       /// </summary>
       public string Model
       {
           get { return this.model; }
       }

       /// <summary>
       /// Property to set the direction (heading) of the
       /// airplane.  Will be a bearing between 0 and 359 degrees
       /// where 0 is north, 90 east, 180 south, and 270 west.
       /// Range checking is performed to keep the direction
       /// between 0 inclusive and 360 exclusive.
       /// </summary>
       public float Direction
       {
           get { return this.direction; }
           set
           {
               float temp = value;
               if (Math.Abs(temp) >= 360.0F)
               {
                   temp = temp % 360.0F;
               }
               if (temp < direction =" temp;">
       /// A property to get or set the Altitude of an airplane.
       /// Range checking is used to prevent flying past the MaxAltitude.
       /// </summary>
       public int Altitude
       {
           get { return this.altitude; }
           set {
               if (value < altitude =" 0;"> MaxAltitude)
               {
                   Console.WriteLine("Can't fly that high: " + value);
                   this.altitude = MaxAltitude;
               }
               else
               {
                   this.altitude = value;
               }
           }
       }

       /// <summary>
       /// A property to get or set the speed.
       /// Range checking is used to prevent flying past the maximum speed.
       /// </summary>
       public int Speed
       {
           get { return this.speed; }
           set
           {
               if (value < speed =" 0;"> MaxSpeed)
               {
                   Console.WriteLine("Can't fly that fast: " + value);
                   this.speed = MaxSpeed;
               }
               else
               {
                   this.speed = value;
               }
           }
       }

       /// <summary>
       /// A constructor used
       /// to create a new Airplane instance.
       /// </summary>
       /// <param name="manufacturer">The manufacturer of the airplane
       /// <param name="model">The model of the airplane
       /// <param name="maxAltitude">The maximum altitude that
       /// this plane can cruise.
       public Airplane(string manufacturer, string model, int maxAltitude)
       {
           this.manufacturer = manufacturer;
           this.model = model;
           MaxAltitude = maxAltitude; // can only be set here.
           this.altitude = 0;
           this.direction = 0;
           this.speed = 0;
       }

       // Make the airplane takeoff
       public void TakeOff ()
       {
           this.altitude = 30000;
           this.speed = 500;
       }
      
       // Make the airplane land
       public void Land ()
       {
           this.altitude = 0;
           this.speed = 0;
       }

       // Set a new course for the airplane
       public void SetCourse (int newDirection)
       {
           this.direction = newDirection;
       }

       // override is used because all objects
       // have a default ToString() method, we want
       // to override that default method with this
       // new method.
       public override string ToString()
       {
           string message;
           if (altitude == 0)
           {
               message = this.manufacturer + " " + this.model +
               " is currently on the ground.";
           }
           else
           {
               message = this.manufacturer + " " + this.model +
                   " flying " + this.speed + " mph" +
                   " with bearing " + this.direction +
                   " at an altitude of " + this.altitude + " ft.";
           }
           return message;
       }
  
       public static void Main ()
       {
           // Create an airplane (Boeing 747) with a max altitude of 40000 ft.
           Airplane airplane1 = new Airplane ("Boeing", "747", 40000);
           // Make it take off
           airplane1.TakeOff();
           airplane1.SetCourse (90);
           // Display information about the airplane.
           // Notice, that ToString() is called automatically by WriteLine.
           Console.WriteLine(airplane1);
          
           // Now increase speed a lot!
           airplane1.Speed *= 100;
           // Try to fly really high.
           airplane1.Altitude = 100000;
           Console.WriteLine(airplane1);

           // Now land.
           airplane1.Land();
           Console.WriteLine(airplane1);
           Console.ReadLine();
       }
   }
}

This class is still fairly simple. Cut & paste it into an editor, save, compile and run it to see the results.

One other thing to note, XML-style comments were used above. These allow for the creation of very professional documentation directly from your source code.

MSDN Link: XML Commenting

Tool link: Ndoc

Tutorial 1 - Classes, objects and methods

This post was from my original website, but giving it a new home here.

This will quickly introduce you to classes, objects and methods in C#. It pretty much just jumps right in. If you'd prefer to see a hello world example, go here.

Three important terms in object oriented programming are classes, objects, and methods. Sun defines objects and classes as follows:

  • An object is a software bundle of related variables and methods. Software objects are often used to model real-world objects you find in everyday life.
  • A class is a blueprint or prototype that defines the variables and the methods common to all objects of a certain kind.

In common terminology, an object is an instance of a class.

A method is basically a function in C++, but unlike C++, they always belong to classes. That is, classes contain methods. Think of methods as the "verbs" of an object/class. They do things.

Airplane Example

Let's take an example of an airplane. We will create a class called Airplane that defines an airplane. We will define an airplane as having a manufacturer, model, altitude, direction and speed. These are all stored in fields. We will also define actions for the airplane like "TakeOff", "Land", "SetCourse" etc. These are all methods for the airplane. While there are many other things that make an airplane, these are the only ones we care about. This is abstraction: taking a complicated thing like an airplane and only dealing with certain parts of interest.

Here's some C# to define an Airplane class, and to create a specific airplane object. In this example, our instance of an airplane is a Boeing 747 traveling at 30,000 feet directly east.

Notes: I am ignoring access modifiers here (public vs. private) and also am not using a great aspect of C# called properties. We'll take a look at those next.

using System;
namespace Tutorial
{
  public class Airplane
  {
      string manufacturer;
      string model;
      int altitude;
      int direction;
      int speed;
      // This function is a constructor. It is used
      // to create a new Airplane instance (or object).
      public Airplane(string manufacturer, string model)
      {
          this.manufacturer = manufacturer;
          this.model = model;
          this.altitude = 0;
          this.direction = 0;
          this.speed = 0;
      }

      // Make the airplane takeoff
      public void TakeOff ()
      {
          this.altitude = 30000;
          this.speed = 500;
      }

      // Make the airplane land
      public void Land ()
      {
          this.altitude = 0;
          this.speed = 0;
      }
      
      // Set a new course for the airplane
      public void SetCourse (int newDirection)
      {
          this.direction = newDirection;
      }
      
      // override is used because all objects
      // have a default ToString() method, we want
      // to override that default method with this
      // new method.
      public override string ToString()
      {
          string message;
          if (altitude == 0)
          {
              message = this.manufacturer + " " + this.model +
              " is currently on the ground.";
          }
          else
          {
              message = this.manufacturer + " " + this.model +
              " flying " + this.speed + " mph" +
              " with bearing " + this.direction +
              " at an altitude of " + this.altitude + " ft.";
          }
          return message;
      }

      public static void Main ()
      {
          // Create an airplane.
          Airplane airplane1 = new Airplane ("Boeing", "747");
          // Make it take off
          airplane1.TakeOff();
          // Set a course 90 degrees (east)
          airplane1.SetCourse (90);
          // Display information about the airplane
          Console.WriteLine(airplane1.ToString());
          Console.ReadLine();
      }
  } // End of class
} // End of namespace

Examining the program

Let's look at some major parts of the above program.

Namespaces

The first few lines deal with namespaces.

using System;
namespace AirplaneTutorial
{ . . . }

Namespaces provide scope and group classes together by function (web, database, windows forms, etc.) System is a broad namespace that contains many other sub-namespaces. It also contains classes such as Console, which is used for reading and writing to the console. The full name for the function to write to the console is:

System.Console.WriteLine("Write to screen");

The directive using indicates that we are using classes from the System namespace and allows us to shorten the method call to just:

Console.WriteLine("Write to screen");

The keyword namespace creates a new namespace called "Tutorial" for our class. Other people wanting to use our class would reference it as Tutorial.Airplane. Or they could use the statement: using Tutorial; and then reference our Airplane directly.

Classes

Next we declare our Airplane class. All of the code between the braces belongs to the class Airplane. The code describes what an airplane is, how to create one, and how to use one.

public class Airplane
{ . . . }

As described earlier, our class contains fields to store information about an airplane and methods to make an airplane do things. There is also a special method called Main. This is where the program begins running.

As it runs

Let's analyze what happens as the program runs. The program begins running in the Main function.

public static void Main ()
{

The first line of Main creates a new airplane.

// Create an airplane.
Airplane airplane1 = new Airplane ("Boeing", "747");

The first two words (Airplane airplane1) define a variable, airplane1, which will hold a reference to an Airplane object. Objects like Airplane are stored on the heap, which is an area of memory in the computer. We need a way to access objects, and variables give us a way to do that. A good analogy is this:

Objects are like balloons. References are strings to balloons. And variables are like your hand. You hold a reference, which is connected to an object (balloon). The line of code above is creating a new balloon (an Airplane object), and giving your hand (the variable airplane1) a string (reference) to it.

The keyword "new" followed by a class name "Airplane" means we are creating a new Airplane object. This means a specific area of memory has now been reserved on the heap for one instance of an airplane. Next we need to create the airplane. This is the job of constructors.

A constructor is a special method used to create new objects. It is a method that has the same name as the class. This is our Airplane constructor, which requires a manufacturer and model.

// This function is a constructor. It is used
// to create a new Airplane instance (or object).
public Airplane(string manufacturer, string model)
{
  this.manufacturer = manufacturer;
  this.model = model;
  this.altitude = 0;
  this.direction = 0;
  this.speed = 0;
}

When the function is called, it is passed strings representing the manufacturer and model. Memory on the heap has already been reserved for the new airplane instance, but we need to initialize it here. The keyword "this" means that we are updating variables for this (new) instance of an airplane. (Note: the value for altitude, direction, and speed would all default to 0 since that is the default value of an int. Setting them to 0 in the constructor accomplishes nothing.)

Next, we run methods on our airplane instance. We make it takeoff and set a new

course. These run the methods (functions) described by the Airplane class. TakeOff will set our altitude and speed, while setCourse will change our direction of travel.

// Make it take off
airplane1.TakeOff();
// Set a course 90 degrees (east)
airplane1.SetCourse (90);

Finally, we can see information about the airplane by calling a method ToString().

// Display information about the airplane
Console.WriteLine(airplane1.ToString());
Console.ReadLine();

Running the program

To run the above program, select and copy it to the clipboard.

To open it in Visual Studio .NET, create a new console application project (File | New Project | C# Console Application). Give it a name. Then open class1.cs, delete the contents of the file and paste the above code into the file. Hit F5 to run it.

From the command line, make sure csc.exe (the C# compiler) is in your path. Typically something like:
C:\WINDOWS\Microsoft.NET\Framework\v1.0.3705 - for version 1.0 of .NET framework.
C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322 - for version 1.1 of .NET framework.

Copy and paste the above program into a text editor and save as airplane1.cs

> csc airplane1.cs

The resulting airplane1.exe is the compiled program. Run it and it should print out the following line and then wait for you to hit "Enter" before exiting.

> airplane1.exe
Boeing 747 flying 500 mph with bearing 90 at an altitude of 30000 ft.

That's a pretty simple, but complete, C# program.