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!