🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

C# Workshop - Week 2 Exercises

Started by
5 comments, last by cNoob 16 years, 11 months ago
C# Workshop - Week 2 Exercises Since we're still very early on in the workshop and since there are quite a few people new to programming in this workshop, I wanted the first few weeks of exercises to serve not only as an opportunity for you to try things out on your own, but also for you to get more exposure to seeing C# code. As a result, I've decided to structure the exercises for the first few weeks so that the source code for the exercise is largely provided for you. This makes it appear much more like an overview and removes the need for you to determine the correct syntax for the requested exercise, and instead allows you to focus on the behavior being exercised and upon the generated output. To get the most from these types of exercises you must thoroughly read the instructions for each exercise, then type the code listed below it into the compiler yourself, Compile, Run, and Observe the results. Often times compiles will fail. This is frequently intentional and will be indicated so when it is a likely result. This is a time consuming process for me so if you appreciate the added clarity caused by seeing all the code typed out, please let me know. Main Entry Point 1a. Create a program which has a main entry point as void Main()

class Program
{
    static void Main()
    {
        
    }
}

1b. Now int Main() - notice you must return a value

class Program
{
    static int Main()
    {
        return 0;
    }
}

1c. Now with void main(string[] args). Either run the executable from the command line and add additional arguments, such as: C:\ConsoleApplication1 Hello World or Right click on the project name in the solution explorer and go to the Debug tab. Here, you may add command-line arguments to be used when you run the program from within the Visual Studio debugger.

class Program
{
    static void Main(string[] args)
    {
        foreach (string argument in args)
        {
            System.Console.WriteLine(argument);
        }
    }
}

1d. Attempt to create 2 mains in the same class. Compile this, observe the error.

class Program
{
    static void Main(string[] args)
    {
    }

    static void Main()
    {
    }
}


1e. Attempt to create main's in multiple classes. Compile this, observe the error. Then, open up your project's settings by right clicking on the project name in the solution explorer. On the application tab there is a DropDown List titled "Startup Object." Select one of the two objects, either Program or Program2. Close the properties and compile again.

class Program
{
    static void Main(string[] args)
    {
    }
}

class Program2
{
    static void Main()
    {
    }
}

Reference Types vs. Value Types 2a. Create an object of type 'System.Object' and set its reference to null

class Program
{
    static void Main()
    {
        System.Object myObject = null;
    }
}

2b. Create an integer or other value type and try setting it to null. Observe the compile error.

class Program
{
    static void Main()
    {
        int myInteger = null;
    }
}

3a. Create two integers. Assign the value of the first to the second. Attempt to change the value in the second variable and observe its impact on the first variable.

class Program
{
    static void Main()
    {
        // Create an integer and set its value to 10
        // Create a second integer and assign it the value of first
        int first = 10;               
        int second = first;

        // Print the value of second
        System.Console.WriteLine("The value of second is: {0}", second);

        // Modify the value of second
        second = 20;

        // Print the value of first, and observe that it is unaffected
        System.Console.WriteLine("The value of first is still: {0}", first);
    }
}

3b. Create a custom value type as a struct. Assign a value of type 'Custom' to a second variable. Attempt to change the value in the second variable and observe its impact on the first variable.

class Program
{
    static void Main()
    {
        // Create a 'custom' variable and set its public field to 10
        Custom custom1 = new Custom();
        custom1.MyInteger = 10;

        // Create a second 'custom' variable, but assign it the value of custom1
        Custom custom2 = custom1;

        // Print the value of MyInteger in custom2
        System.Console.WriteLine("The value of custom 2 is: {0}",custom2.MyInteger);

        // Modify the value of custom2
        custom2.MyInteger = 20;

        // Print the value of MyInteger in custom1, and observe that it is unaffected
        System.Console.WriteLine("The value of custom 1 is now: {0}", custom1.MyInteger);
    }
}

struct Custom
{
    public int MyInteger;
}

3c. Create a custom reference type. Assign an object of type 'Custom' to a second object. Attempt to change the value in the second reference and observe its impact on the first object.

class Program
{
    static void Main()
    {
        // Create a 'custom' object and set its public field to 10
        Custom custom1 = new Custom();
        custom1.MyInteger = 10;

        // Create a second 'custom' object, but assign it a reference
        // to custom1
        Custom custom2 = custom1;

        // Print the value of MyInteger in custom2
        System.Console.WriteLine("The value of custom 2 is: {0}",custom2.MyInteger);

        // Modify the value
        custom2.MyInteger = 20;

        // Print the value of MyInteger in custom1
        System.Console.WriteLine("The value of custom 1 is now: {0}", custom1.MyInteger);
    }
}

class Custom
{
    public int MyInteger;
}

3d. Modify 3c by creating two instances of the custom object, each with their own values. Modify one, Compile, Run, and observe the impact upon the other object.

class Program
{
    static void Main()
    {
        // Create a 'custom' object and set its public field to 10
        Custom custom1 = new Custom();
        custom1.MyInteger = 10;

        // Create a second 'custom' object with a value of 20
        Custom custom2 = new Custom();
        custom2.MyInteger = 20;

        // Print the value of MyInteger in custom2
        System.Console.WriteLine("The value of custom 1 is: {0}", custom1.MyInteger);
        System.Console.WriteLine("The value of custom 2 is: {0}",custom2.MyInteger);

        // Modify the value
        custom2.MyInteger = 20;

        // Print the value of MyInteger in custom1
        System.Console.WriteLine("");
        System.Console.WriteLine("The value of custom 1 is now: {0}", custom1.MyInteger);
        System.Console.WriteLine("The value of custom 2 is now: {0}", custom2.MyInteger);
    }
}

class Custom
{
    public int MyInteger;
}

3e. Modify 3d. by making the member variable 'static'. Compile, Observe the errors

// ...

class Custom
{
    public static int MyInteger;
}

3f. Modify 3e by replacing all instances of custom1.MyInteger or custom2.MyInteger with Custom.MyInteger. You may optionally remove the objects as they are unneeded. When working with static members, you're referring to the members ON the class, NOT ON the object.

class Program
{
    static void Main()
    {        
        Custom.MyInteger = 10;

        // Print the value of MyInteger
        System.Console.WriteLine("The value of Custom.MyInteger is: {0}", Custom.MyInteger);

        // Modify the value
        Custom.MyInteger = 20;

        // Print the value of MyInteger        
        System.Console.WriteLine("The value of Custom.MyInteger is now: {0}", Custom.MyInteger);
    }
}

class Custom
{
    public static int MyInteger;
}

Simple Value Types 4a. Use the struct methods with each of the simple data types

class Program
{
    static void Main()
    {
        sbyte mySbyte = 0;      // sbyte is just an alias for System.SByte
        byte myByte = 0;        // byte is just an alias for System.Byte
        short myInt16 = 0;      // short is just an alias for System.Int16
        ushort myUInt16 = 0;    // ushort is just an alias for System.UInt16
        int myInt32 = 0;        // int is just an alias for System.Int32
        uint myUInt32 = 0;      // uint is just an alias for System.UInt32
        long myInt64 = 0;       // long is just an alias for System.Int64
        ulong myUInt64 = 0;     // ulong is just an alias for System.UInt64
        char myChar = '0';      // char is just an alias for System.Char
        float mySingle = 0;     // float is just an alias for System.Single
        double myDouble = 0;    // double is just an alias for System.Double
        decimal myDecimal = 0;  // decimal is just an alias for System.Decimal
        bool myBoolean = false; // bool is just an alias for System.Boolean

        // Each of the simple types are just alias for Structures defined in the System
        // namespace.  As a result, you can call the methods of System.ValueType on any
        // variable of a simple type.  This includes the 'GetType' Method which demonstrates
        // the aliases
        System.Console.WriteLine("{0}", mySbyte.GetType());
        System.Console.WriteLine("{0}", myByte.GetType());
        System.Console.WriteLine("{0}", myInt16.GetType());
        System.Console.WriteLine("{0}", myUInt16.GetType());
        System.Console.WriteLine("{0}", myInt32.GetType());
        System.Console.WriteLine("{0}", myUInt32.GetType());
        System.Console.WriteLine("{0}", myInt64.GetType());
        System.Console.WriteLine("{0}", myUInt64.GetType());
        System.Console.WriteLine("{0}", myChar.GetType());
        System.Console.WriteLine("{0}", mySingle.GetType());
        System.Console.WriteLine("{0}", myDouble.GetType());
        System.Console.WriteLine("{0}", myDecimal.GetType());
        System.Console.WriteLine("{0}", myBoolean.GetType());
    }
}

4b. Add the sizeof operator to each of the above writelines in order to determine the sizes of each type. Compile, Run, and Observe the output.

class Program
{
    static void Main()
    {
        sbyte mySbyte = 0;      // sbyte is just an alias for System.SByte
        byte myByte = 0;        // byte is just an alias for System.Byte
        short myInt16 = 0;      // short is just an alias for System.Int16
        ushort myUInt16 = 0;    // ushort is just an alias for System.UInt16
        int myInt32 = 0;        // int is just an alias for System.Int32
        uint myUInt32 = 0;      // uint is just an alias for System.UInt32
        long myInt64 = 0;       // long is just an alias for System.Int64
        ulong myUInt64 = 0;     // ulong is just an alias for System.UInt64
        char myChar = '0';      // char is just an alias for System.Char
        float mySingle = 0;     // float is just an alias for System.Single
        double myDouble = 0;    // double is just an alias for System.Double
        decimal myDecimal = 0;  // decimal is just an alias for System.Decimal
        bool myBoolean = false; // bool is just an alias for System.Boolean

        // Each of the simple types are just alias for Structures defined in the System
        // namespace.  As a result, you can call the methods of System.ValueType on any
        // variable of a simple type.  This includes the 'GetType' Method which demonstrates
        // the aliases
        System.Console.WriteLine("{0} is {1} bytes", mySbyte.GetType(), sizeof(sbyte) );
        System.Console.WriteLine("{0} is {1} bytes", myByte.GetType(), sizeof(byte));
        System.Console.WriteLine("{0} is {1} bytes", myInt16.GetType(), sizeof(short));
        System.Console.WriteLine("{0} is {1} bytes", myUInt16.GetType(), sizeof(ushort));
        System.Console.WriteLine("{0} is {1} bytes", myInt32.GetType(), sizeof(int));
        System.Console.WriteLine("{0} is {1} bytes", myUInt32.GetType(), sizeof(uint));
        System.Console.WriteLine("{0} is {1} bytes", myInt64.GetType(), sizeof(long));
        System.Console.WriteLine("{0} is {1} bytes", myUInt64.GetType(), sizeof(ulong));
        System.Console.WriteLine("{0} is {1} bytes", myChar.GetType(), sizeof(char));
        System.Console.WriteLine("{0} is {1} bytes", mySingle.GetType(), sizeof(float));
        System.Console.WriteLine("{0} is {1} bytes", myDouble.GetType(), sizeof(double));
        System.Console.WriteLine("{0} is {1} bytes", myDecimal.GetType(), sizeof(decimal));
        System.Console.WriteLine("{0} is {1} bytes", myBoolean.GetType(), sizeof(bool));             
    }
}

4c. Modify 4b by Assigning different values to each of the local variables more appropriate to their type. Compile, Run, and Observe.

class Program
{
    static void Main()
    {
        sbyte   mySbyte     = 127;    
        byte    myByte      = 255;      
        short   myInt16     = 32767;      
        ushort  myUInt16    = 65535;
        int     myInt32     = 2147483647;
        uint    myUInt32    = 4294967295;
        long    myInt64     = 9223372036854775807;
        ulong   myUInt64    = 18446744073709551615;     
        char    myChar      = 'J';      
        float   mySingle    = 2.0f/3.0f; // The 'f' is required after the number to indicate it is a 'float'
        double  myDouble    = 2.0 / 3.0; // No symbol is required, as double is C#'s default
        decimal myDecimal   = 2.0m / 3.0m; // The 'm' is required after the number to indicate it is a decimal    
        bool    myBoolean   = true; 

        System.Console.WriteLine("{0}'s max value is {1}", mySbyte.GetType(), mySbyte);
        System.Console.WriteLine("{0}'s max value is {1}", myByte.GetType(), myByte);
        System.Console.WriteLine("{0}'s max value is {1}", myInt16.GetType(), myInt16);
        System.Console.WriteLine("{0}'s max value is {1}", myUInt16.GetType(), myUInt16);
        System.Console.WriteLine("{0}'s max value is {1}", myInt32.GetType(), myInt32);
        System.Console.WriteLine("{0}'s max value is {1}", myUInt32.GetType(), myUInt32);
        System.Console.WriteLine("{0}'s max value is {1}", myInt64.GetType(), myInt64);
        System.Console.WriteLine("{0}'s max value is {1}", myUInt64.GetType(), myUInt64);       
        System.Console.WriteLine("{0}'s precision is up to 7 digits: {1}", mySingle.GetType(), mySingle);
        System.Console.WriteLine("{0}'s precision is up to 15 digits: {1}", myDouble.GetType(), myDouble);
        System.Console.WriteLine("{0}'s precision is up to 28 digits: {1}", myDecimal.GetType(), myDecimal);
        System.Console.WriteLine("{0}'s can also be {1}",  myBoolean.GetType(), myBoolean);
        System.Console.WriteLine("{0}'s can also be other letters such as {1}", myChar.GetType(), myChar);
    }
}

4d. Modify 4c for fun by taking any of the "max value" lines and adding 1 to the initially assigned value. Compile and observe the error(s). 4e. Modify 4b by assigning the variables a new value after the initial display.

class Program
{
    static void Main()
    {
        // ... same as before
  
        mySbyte = -128;
        myByte = 0;
        myInt16 = -32768;
        myUInt16 = 0;
        myInt32 = -2147483648;
        myUInt32 = 0;
        myInt64 = -9223372036854775808;
        myUInt64 = 0;

        System.Console.WriteLine("");
        System.Console.WriteLine("{0}'s min value is {1}", mySbyte.GetType(), mySbyte);
        System.Console.WriteLine("{0}'s min value is {1}", myByte.GetType(), myByte);
        System.Console.WriteLine("{0}'s min value is {1}", myInt16.GetType(), myInt16);
        System.Console.WriteLine("{0}'s min value is {1}", myUInt16.GetType(), myUInt16);
        System.Console.WriteLine("{0}'s min value is {1}", myInt32.GetType(), myInt32);
        System.Console.WriteLine("{0}'s min value is {1}", myUInt32.GetType(), myUInt32);
        System.Console.WriteLine("{0}'s min value is {1}", myInt64.GetType(), myInt64);
        System.Console.WriteLine("{0}'s min value is {1}", myUInt64.GetType(), myUInt64);
    }
}

4f. Modify 4d. by making the local variables constant. By adding 'const in front we have made them symbolic constants, and thus, they are no longer variables and cannot be re-assigned to. Compile, observe the errors.

class Program
{
    static void Main()
    {
        const sbyte     mySbyte	= 127;
        const byte      myByte 	= 255;
        const short     myInt16 	= 32767;
        const ushort    myUInt16 	= 65535;
        const int       myInt32 	= 2147483647;
        const uint      myUInt32 	= 4294967295;
        const long      myInt64 	= 9223372036854775807;
        const ulong     myUInt64 	= 18446744073709551615;          

        mySbyte = -128;
        myByte = 0;
        myInt16 = -32768;
        myUInt16 = 0;
        myInt32 = -2147483648;
        myUInt32 = 0;
        myInt64 = -9223372036854775808;
        myUInt64 = 0;
    }
}

4g. Removes the 'const' keywords and experiment with names of variables. Use a combination of letters, numbers, and underscores. Attempt to put each in different places, with special attention to the beginning of the variable name. There is nothing more to this exercises than getting familiar with what symbols and characters are allowed in identifier names. Overflow & Underflow 5a. Now we are going to experiment with underflow and overflow by adding 1 to the value of some of the variables from example 4. This time, rather than as part of the declaration, as part of a mathematical expression. For overflow, the value will be too large for the variable to hold. For underflow, the value will be too small. Note that by adding 1 to the value of a variable's maximum value it is equal to its minimum value. By subtracting 1 from the minimum value it is equal to its maximum value. Compile, Run, and Observe the following.

class Program
{
    static void Main()
    {
        int myInt32 = 2147483647;
        uint myUInt32 = 4294967295;
        long myInt64 = 9223372036854775807;
        ulong myUInt64 = 18446744073709551615;

        myInt32  = myInt32  + 1;
        myUInt32 = myUInt32 + 1;
        myInt64  = myInt64  + 1;
        myUInt64 = myUInt64 + 1;

        System.Console.WriteLine("{0}'s min value is {1}", myInt32.GetType(), myInt32);
        System.Console.WriteLine("{0}'s min value is {1}", myUInt32.GetType(), myUInt32);
        System.Console.WriteLine("{0}'s min value is {1}", myInt64.GetType(), myInt64);
        System.Console.WriteLine("{0}'s min value is {1}", myUInt64.GetType(), myUInt64);

        myInt32 = myInt32 - 1;
        myUInt32 = myUInt32 - 1;
        myInt64 = myInt64 - 1;
        myUInt64 = myUInt64 - 1;

        System.Console.WriteLine("");
        System.Console.WriteLine("{0}'s max value is {1}", myInt32.GetType(), myInt32);
        System.Console.WriteLine("{0}'s max value is {1}", myUInt32.GetType(), myUInt32);
        System.Console.WriteLine("{0}'s max value is {1}", myInt64.GetType(), myInt64);
        System.Console.WriteLine("{0}'s max value is {1}", myUInt64.GetType(), myUInt64);
    }
}

5b. Now we're going to perform the same action as before, but we're going to add in the keyword 'checked' before and after the mathematical operations. Compile, Run, and Observe the following.

class Program
{
    static void Main()
    {
        int myInt32 = 2147483647;
        // ...

        checked
        {
            myInt32 = myInt32 + 1;
            // ...
        }

        System.Console.WriteLine("{0}'s min value is {1}", myInt32.GetType(), myInt32);
        // ...

        checked
        {
            myInt32 = myInt32 - 1;
            // ...
        }

        System.Console.WriteLine("");
        // ...
    }
}

5c. Now change the 'checked' to 'unchecked'. Note that specifying 'unchecked' is the same as not specifying anything. In other words, unchecked is the default behavior. Compile, Run, and Observe the following.

class Program
{
    static void Main()
    {
        int myInt32 = 2147483647;
        // ...

        checked
        {
            myInt32 = myInt32 + 1;
            // ...
        }

        System.Console.WriteLine("{0}'s min value is {1}", myInt32.GetType(), myInt32);
        // ...

        checked
        {
            myInt32 = myInt32 - 1;
            // ...
        }

        System.Console.WriteLine("");
        // ...
    }
}

Implicit Conversions 6a. Returning to some of the code from 5c, we're going to experiment with some implicit conversions of simple types. Compile, Run, and Observe the following. Pay special attention to the lack of any warnings and the fact that you're capable of copying data from one type to another without alarm.

class Program
{
    static void Main()
    {
        sbyte mySbyte = 127;
        uint myUInt32 = 4294967295;
        char myChar = 'J';
        float mySingle = 2.0f / 3.0f; // The 'f' is required after the number to indicate it is a 'float'

        // Implicitly convert from a signed byte, to all supported types
        short   tempShort   = mySbyte;
        int     tempInt     = mySbyte;
        long    tempLong    = mySbyte;
        float   tempFloat   = mySbyte;
        double  tempDouble  = mySbyte;
        decimal tempDecimal = mySbyte;

        System.Console.WriteLine("tempShort's value is {0}",  tempShort);
        System.Console.WriteLine("tempInt's value is {0}",  tempInt);
        System.Console.WriteLine("tempLong's value is {0}",  tempLong);
        System.Console.WriteLine("tempFloat's value is {0}",  tempFloat);
        System.Console.WriteLine("tempDouble's value is {0}",  tempDouble);
        System.Console.WriteLine("tempDecimal's value is {0}",  tempDecimal);

        // Implicitly convert from a unsigned integer, to all supported types
        ulong tempULong = myUInt32;
        tempLong = myUInt32;
        tempFloat = myUInt32;
        tempDouble = myUInt32;
        tempDecimal = myUInt32;

        System.Console.WriteLine("tempULong's value is {0}", tempULong);
        System.Console.WriteLine("tempLong's value is {0}", tempLong);
        System.Console.WriteLine("tempFloat's value is {0}", tempFloat);
        System.Console.WriteLine("tempDouble's value is {0}", tempDouble);
        System.Console.WriteLine("tempDecimal's value is {0}", tempDecimal);

        // You can implicitly convert from any type larger than an unsigned short (16 bit w/ range beginning at 0)
        // The value is the character's Unicode value
        tempULong = myChar;
        System.Console.WriteLine("tempULong's value is {0}", tempULong);

        // Floats can only be implicitly converted to double.  Note however the garbage data created by taking
        // a float and giving it increased accuracy
        tempDouble = mySingle;
        System.Console.WriteLine("tempDouble's value is {0}", tempDouble);
    }
}

6b. The numerical value 0 can be implicitly converted into any enum type. However, no other numerical value can be explicitly converted. Compile, Run, and Observe the following.

class Program
{
    enum HairColor
    {
        Brown,
        Red,
        Black,
        Gray
    }

    static void Main()
    {
        HairColor myHairColor = 0;
        System.Console.WriteLine("My hair color is: {0}", myHairColor);
    }
}

6c. As all objects are implicitly of type 'Object' due to inheritance, you can implicitly convert all objects to 'object'. Compile, Run, and Observe the lack of warnings or errors.

class Program
{  
    static void Main()
    {
        MyClass myObject = new MyClass();
        object tempObject = myObject;
    }
}

class MyClass
{

}

6d. As all simple types are implicitly of type 'value type' and thus also 'object' it is possible to convert any simple type into an object. This is referred to as 'Boxing'. Compile the following.

class Program
{  
    static void Main()
    {
        // Note the integer is both a System.ValueType and a System.Object
        int myInteger = 0;
        System.ValueType tempValue = myInteger;
        object tempObject = myInteger;

        // Create an instance of myStruct.  Remember, structs are value-types like integers, and thus can
        // be allocated on the stack and do not require a 'new'.  
        MyStruct myStruct;
        tempValue = myStruct;
        tempObject = myStruct;
    }
}

struct MyStruct
{
}

Explicit Conversions 7a. We're now going to experiment with some explicit conversions of simple types. Compile, Run, and Observe the following. Pay special attention to the errors.

class Program
{
    static void Main()
    {
        byte    myByte = 129;
        int     myInt32 = -100;
        float   mySingle = 2.0f / 3.0f;
        double  myDouble = 2.0 / 3.0;

        // Implicitly convert from an unsigned byte, to all supported types
        sbyte tempSByte = myByte;
        char tempChar = myByte;

        System.Console.WriteLine("tempSByte's value is {0}", tempSByte);
        System.Console.WriteLine("tempChar's value is {0}", tempChar);

        // Convert from a signed integer, to an unsigned integer.  Any negative values will be treated as underflow
        // and will wrap around
        uint tempUInt = myInt32;
        System.Console.WriteLine("tempUInt's value is {0}", tempUInt);

        // When casting a float to an integer, it truncates the decimal, and risks overflow
        // in this case, a 0.66666... is just 0 as far as integers are concerned.
        int tempInt32 = mySingle;
        System.Console.WriteLine("tempInt32's value is {0}", tempInt32);

        // Converting from double to single causes a loss of precision, and inherent rounding errors
        // However, this is only a real problem for non-repeating values greater than 7 digits
        float tempSingle = myDouble;
        System.Console.WriteLine("tempSingle's value is {0}", tempSingle);
    }
}

7b. Unable to compile with the need for explicit conversion indicators, repeat the Compile of 7a, but include the following explicit conversion indicators.

class Program
{
    static void Main()
    {
        byte    myByte = 129;
        int     myInt32 = -100;
        float   mySingle = 2.0f / 3.0f;
        double  myDouble = 2.0 / 3.0;

        // Implicitly convert from an unsigned byte, to all supported types
        sbyte tempSByte = (sbyte)myByte;
        char tempChar = (char)myByte;

        System.Console.WriteLine("tempSByte's value is {0}", tempSByte);
        System.Console.WriteLine("tempChar's value is {0}", tempChar);

        // Convert from a signed integer, to an unsigned integer.  Any negative values will be treated as underflow
        // and will wrap around
        uint tempUInt = (uint)myInt32;
        System.Console.WriteLine("tempUInt's value is {0}", tempUInt);

        // When casting a float to an integer, it truncates the decimal, and risks overflow
        // in this case, a 0.66666... is just 0 as far as integers are concerned.
        int tempInt32 = (int)mySingle;
        System.Console.WriteLine("tempInt32's value is {0}", tempInt32);

        // Converting from double to single causes a loss of precision, and inherent rounding errors
        // However, this is only a real problem for non-repeating values greater than 7 digits
        float tempSingle = (float)myDouble;
        System.Console.WriteLine("tempSingle's value is {0}", tempSingle);
    }
}

7c. While it is possible to implicitly convert from 0 to any enum type, it is not possible to implicitly convert any other number to an enum type. Nor to convert back from an enum to another type. For this, explicit conversion is required. Compile, Run, Observe the following.

class Program
{
    enum HairColor
    {
        Brown,
        Red,
        Black,
        Gray
    }

    static void Main()
    {
        HairColor myHairColor = (HairColor)2;
        System.Console.WriteLine("My hair color is: {0}", myHairColor);

        int myInteger = (int)myHairColor;
        System.Console.WriteLine("My integer is: {0}", myInteger);
    }
}

7d. When dealing with explicit casts of reference types its important to remember there are two types of casts - static, and dynamic. With a static cast the programmer is attempting to force a cast, even if types aren't implicitly compatible. This can cause an exception. Compile, Run, and Observe the following. Pay special attention to the run-time exception.

class Program
{  
    static void Main()
    {
	  // This throws an exception because while MyClass is a specialization of an Object,
        // the reverse is not true.  Thus, they are incompatible in this direction.
        MyClass myObject = (MyClass)(new System.Object());
    }
}

class MyClass
{
}

7e. However, using static casting with reference types doesn't have to result in an exception if, at run-time, the types actually ARE compatible. Compile, Run, and Observe the following.

class Program
{  
    static void Main()
    {
        object myObject = new MyClass();

        // This does not cause an exception because myObject actually IS a MyClass
        MyClass myClass = (MyClass)myObject;
    }
}

class MyClass
{
}

7f. The alternative, safer way to explicitly cast reference types is to use dynamic casting. this method uses the 'as' operator to perform an explicit cast, only if it's safe. If its unable to perform the cast, the variable being assigned to will receive 'null' instead. With this form of casting it's essential to check the return value before using. Compile, Run, and Observe the following.

class Program
{
    static void Main()
    {
        // This does not throw an exception in spite of the fact they are incompatible types.
        // instead, myObject simply receives the value 'null'.
        MyClass myObject = new System.Object() as MyClass;

        object myTempObject = new MyClass();

        // Anywhere a static cast worked, a dynamic cast will also work
        // So as before this is a valid explicit cast, and myClass receives a reference to myTempObject
        // ...now operating as a MyClass object
        MyClass myClass = myTempObject as MyClass;
    }
}

class MyClass
{
}

7g. Unboxing Conversion. Compile, Run, Observe the following

class Program
{
    static void Main()
    {
        int myInteger = 10;

        // We perform the implicit boxing conversion as before, changing a value type into a 
        // reference type
        object tempObject = myInteger;

        // But now we add in the explicit cast BACK to the valuetype
        int myNewInteger = (int)tempObject;

        System.Console.WriteLine("My new integer has the value: {0}", myNewInteger);
    }
}

7h. It's important to note when doing unboxing, that the type being unboxed to must match EXACTLY to the type which was originally boxed. Even if the two value types were compatible. Compile, Run, observe the following. Pay special attention to the run-time exception cased by the last line.

class Program
{
    static void Main()
    {
        int myInteger = 10;

        // We perform the implicit boxing conversion as before, changing a value type into a 
        // reference type
        object tempObject = myInteger;

        // But now we add in the explicit cast BACK to the valuetype
        int myNewInteger = (int)tempObject;

        // This is acceptable because I'm casting tempObject to an integer, which it was,
        // and then the integer is being implicitly casted to a long
        long myLongInteger = (int)tempObject;

        // This is a run-time exception, because even though the value in tempObject can be
        // implicitly converted to a long, it must first be unboxed to it's original type
        // No shortcuts
        myLongInteger = (long)tempObject;        
    }
}

Members & Accessibility 8a. Create public, protected, and private members and then attempt to access them from within a member function (method) as listed below. Compile, Run, and Observe the accessibility.

class Program
{
    static void Main()
    {
        BaseClass baseClass = new BaseClass();
        baseClass.BaseMethod();
    }    
}

class BaseClass
{
    public void BaseMethod()
    {
        MyPublicInt = 1;
        MyProtectedInt = 2;
        MyPrivateInt = 4;

        System.Console.WriteLine("My Public Integer is {0}", MyPublicInt);
        System.Console.WriteLine("My Protected Integer is {0}", MyProtectedInt);
        System.Console.WriteLine("My Private Integer is {0}", MyPrivateInt);
    }

    public    int MyPublicInt;
    protected int MyProtectedInt;
    private   int MyPrivateInt;
}

8b. Modify 8a by adding a derived class. Attempt to access the public, protected, and private fields from within the derived class as follows. Compile and observe the errors.

class Program
{
    // ...     
}

class BaseClass
{
    // ... 
}

class Derived : BaseClass
{
    public void DerivedMethod()
    {
        MyPublicInt = 2;
        MyProtectedInt = 4;
        MyPrivateInt = 8;
    }
}

8c. Delete the Derived class, then attempt to access the public, protected, and private Fields from within Main as follows. Compile and note the errors.

class Program
{
    static void Main()
    {
        BaseClass baseClass = new BaseClass();
        baseClass.MyPublicInt = 1;
        baseClass.MyProtectedInt = 2;
        baseClass.MyPrivateInt = 4;        
    }
}

class BaseClass
{
    // ... 
} 

Cheers!
Jeromy Walsh
Sr. Tools & Engine Programmer | Software Engineer
Microsoft Windows Phone Team
Chronicles of Elyria (An In-development MMORPG)
GameDevelopedia.com - Blog & Tutorials
GDNet Mentoring: XNA Workshop | C# Workshop | C++ Workshop
"The question is not how far, the question is do you possess the constitution, the depth of faith, to go as far as is needed?" - Il Duche, Boondock Saints
Advertisement
good work JWalsh

@Total beginners:
You can run programs step by step by pressing F10 or F11 in VS. It really helps when you see program executing one step at a time.

When running program this way you can also observe variables. You can see values of current local variables in window named "Locals". By default it should be in bottom left corner (if you can't find it: make sure program is running & that it is running in debug mode; otherwise you can open it by going to Debug->Windows->Locals). If you want to observe non-local variables you can type a name of a variable you want to observe into "Watch" window (it is located beside "Locals" window)

I hope this helps
Can someone offer the link to the exercises without the answers posted to them?
I am somehow missing them. Or were these answers posted as a result of a deadline?

Thanks for any info in advance.
I rate users. Its just not having much impact as my rating is low...
These exercises don't really have answers. The point of these exercises is to get familiar with what the code looks like, and to observe the output generated - as well as the compiler warnings and errors I introduced into the code.

The exercises for week 2 were created this way because it is so early in the workshop, and because the textbook we've been using so far has failed to provide sufficient code samples. I want to give people more opportunity to see the code.

To do the exercises for Week 2, enter the lines from each exercise into your IDE. DO NOT copy/paste them. Enter the lines 1 by 1, and make sure you understand what the lines are doing.

Then compile, run, and observe the output from each one.

Week 3 exercises may also be presented in this format, however I've not yet decided, given that we're now using a simpler textbook. Perhaps for week 3 I'll provide half of the exercises in this format, while requiring the second half to be such that users must think up the code on their own.

Exercises after week 3 will, in general, not provide the code for you.

Cheers!
Jeromy Walsh
Sr. Tools & Engine Programmer | Software Engineer
Microsoft Windows Phone Team
Chronicles of Elyria (An In-development MMORPG)
GameDevelopedia.com - Blog & Tutorials
GDNet Mentoring: XNA Workshop | C# Workshop | C++ Workshop
"The question is not how far, the question is do you possess the constitution, the depth of faith, to go as far as is needed?" - Il Duche, Boondock Saints
JWalsh can you come over and type the exercises into my IDE for me and explain them to me? I am confused. :) Our can I just use Ctrl C and Ctrl V?

theTroll
@JWalsh

Thank you for the reply.
I rate users. Its just not having much impact as my rating is low...
By typing each of these out I was able to think what was going to happen before I compiled them and checked whether or not I was right. Seeing some of the compiler errors after typing these out makes sure that I wont make the some of these mistakes further down the line. Thanks Jeromy, must of took alot of time for these.

This topic is closed to new replies.

Advertisement