System types and collections: Using System types


Using System types edit

Exam objective: Manage data in a .NET Framework application by using the .NET Framework 2.0 system types.

(Refer System namespace)

Value types usage edit

Following are some "usage" oriented remarks about value types.

Value types contain the values they are assigned:

int a = 1;  // the variable "a" contains "1" of value type int

Value types can also be created by using the new keyword. Using the new keyword initializes the variable with the default value obtained from the type's default constructor:

int a = new int(); // using the default constructor via the new keyword
return a;          // returns "0" in the case of type Int.

Value types can be declared without being initialized, but they must be initialized to some value before being used:

int a;     // This is perfectly acceptable
return a;  // NOT acceptable!  You can't use "a" because "a" doesn't have a value!

Value types cannot equal null. .NET 2.0 provides a Nullable type to get around this limitation, which is discussed in the next section, but null is not a valid value for value types:

int a = null;  // Won't compile - throws an error.

If you copy a Value type to another Value type, the value is copied. Changing the value of the copy has no effect on the value of the original. The second is merely a copy of the first - they are in no way connected after assignment. This is fairly intuitive:

int var1 = 1;
int var2 = var1;  //the value of var1 (a "1" of type int) is copied to var2
var2 = 25;        // The "1" value in var2 is overwritten with "25"
Console.WriteLine("The value of var1 is {0}, the value of var2 is {1}", var1, var2);

Which would result in the output:

The value of var1 is 1, the value of var2 is 25

Changing the value of the copy (var2 in this instance) had no effect on the value of the original (var1). This is different from reference types which copy a reference to the value, not the value itself.

Value types cannot be derived from.

Value types as method parameters are passed by value by default. A copy of the value-type is made and the copy is passed to the method as a parameter. If the parameter is changed inside the method it will not affect the value of the original value type.


 

To do:
Eventually we could add an example showing the usage of some of the built-in value type: integer, floating point, logical, char and decimal and the value parameter passing of value types


Nullable type edit

See MSDN

A nullable type...

  • Is a generic type
  • Is an instance of System.Nullable struct.
  • Can only be declared on value types.
  • Is declared with System.Nullable<type> or the shorthand type? - the two are interchangeable.
System.Nullable<int> MyNullableInt;  // the long version 
int? MyNullableInt;                  // the short version
  • Accepts the normal range of values of the underlying type, as well as null.
bool? MyBoolNullable;  // valid values: true || false || null

Be careful with nullable booleans! In if, for, while or logical evaluation statements a nullable boolean will equate a null value with false—it will not throw an error.

Methods: T GetValueOrDefault() & T GetValueOrDefault(T defaultValue)
Returns the stored value or the default value if the stored value is set to null.

Properties: HasValue & Value
Nullable types have two read only properties: HasValue and Value.

HasValue is a boolean property that returns true if Value != null. It provides a means to check your type for a non-null value before using it where you might throw an error:

 int? MyInt = null;
 int MyOtherInt;
 MyOtherInt = MyInt.Value + 1;    // Error! You can't add null + 1!!
 if (MyInt.HasValue) MyOtherInt = MyInt.Value + 1; // This is a better way.

Value returns the value of your type, null or otherwise.

int? MyInt = 27;
if (MyInt.HasValue) return MyInt.Value;  // returns 27.
MyInt = null;
return MyInt; // returns null.

Wrapping / Unwrapping

Wrapping is the process of packaging a value m from a non-nullable type N to a nullable type N? via the expression new N?(m)

Unwrapping is the process of evaluating a nullable type N? for instance m as type N or NULL and is performed via the 'Value' property (e.g. m.Value).

Note: Unwrapping a null instance generates the exception System.InvalidOperationException

The ?? Operator (aka the Null Coalescing Operator)

While not for use solely with Nullable types, the ?? operator proves very useful when you want to use a default value instead of a null value. The ?? operator returns the left operand of a statement if not null, otherwise it returns the right operand.

int? MyInt = null;
return MyInt ?? 27;  // returns 27, since MyInt is null

For more information see the blog entry by R. Aaron Zupancic on the ?? Operator

Building a value type edit

Building a value type must be very simple. The following example defines a custom "point" structure with only 2 double members. See boxing and unboxing for a discussion of implicit conversion of value types to reference types.

C# Code sample

Building and using a custom value type (struct)

   using System;
   using System.Collections.Generic;
   using System.Text;
   //
   namespace ValueTypeLab01
   {
       class Program
       {
           static void Main(string[] args)
           {
               MyPoint p;
               p.x = 3.2;
               p.y = 14.1;
               Console.WriteLine("Distance from origin: " + Program.Distance(p));
               // Wait for finish
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
           // method where MyPoint is passed by value
           public static double Distance(MyPoint p)
           {
               return Math.Sqrt(p.x * p.x + p.y * p.y);
           }
       }
       // MyPoint is a struct (custom value type) representing a point
       public struct MyPoint
       {
           public double x;
           public double y;
       }
   }

Using a user-defined value type edit

The above example can be used here. Note that the p variable does not have to be initialized with the new operator.

Using enumerations edit

The following sample shows simple uses of the System enumeration DayOfWeek. The code is much simpler to read than testing for an integer value representing a day. Note that using ToString() on an enum variable will give the string representation of the value (ex. “Monday” instead of “1”).

The possible values can be listed using Reflection. See that section for details.

For a discussion of the Enum class see MSDN

There is a special type of enumeration called a flags enumeration. The exam objectives do not mention it specifically. See MSDN if you are interested.

C# Sample

Simple use of enumerations

   using System;
   using System.Collections.Generic;
   using System.Text;
   //
   namespace EnumLab01
   {
       class Program
       {
           static void Main(string[] args)
           {
               DayOfWeek day = DayOfWeek.Friday;
               if (day == DayOfWeek.Friday)
               {
                   Console.WriteLine("Day: {0}", day);
               }
               DayOfWeek day2 = DayOfWeek.Monday;
               if (day2 < day)
               {
                   Console.WriteLine("Smaller than Friday");
               }
               switch (day)
               {
                   case DayOfWeek.Monday:
                       Console.WriteLine("Monday processing");
                       break;
                   default:
                       Console.WriteLine("Default processing");
                       break;
               }
               int i = (int)DayOfWeek.Sunday;
               Console.WriteLine("Int value of day: {0}", i);
               // Finishing
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
       }
   }

Building an enumeration edit

Building a custom enumeration is pretty straightforward as shown by the following example.

C# Sample

Declaring a simple enumeration

   using System;
   using System.Collections.Generic;
   using System.Text;
   //
   namespace EnumLab02
   {
       class Program
       {
           public enum MyColor
           {
               None = 0,
               Red,
               Green,
               Blue
           }
           static void Main(string[] args)
           {
               MyColor col = MyColor.Green;
               Console.WriteLine("Color: {0}", col);
               // Finishing
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
       }
   }

Using reference types edit

Reference types are more commonly referred to as objects. Classes, Interfaces and Delegates are all reference types, as well as the built-in reference types System.Object and System.String. Reference types are stored in managed Heap memory.

Unlike Value types, reference types can be assigned the value null.

Copying a reference type copies a reference that points to the object, not a copy of the object itself. This can seem counter-intuitive at times, since changing a copy of a reference will also change the original.

A Value type stores the value it is assigned, plain and simple - but a Reference type stores a pointer to a location in memory (on the heap). Think of the heap as a bunch of lockers and the Reference type holds the locker number (there are no locks in this metaphor). Copying a Reference type is like giving someone a copy of your locker number, rather than a copy of its contents. Two Reference types that point to the same memory is like two people sharing the same locker - both can modify its content:

C# Code sample

Example of using Reference types

public class Dog
{
  private string breed;
  public string Breed { get {return breed;} set {breed = value;} }
  
  private int age;
  public int Age { get {return age;} set {age = value;} }
  
  public override string ToString()
  {
    return String.Format("is a {0} that is {1} years old.", Breed, Age);
  }
  
  public Dog(string dogBreed, int dogAge)
  {
    this.breed = dogBreed;
    this.age = dogAge;
  }
}

public class Example()
{
   public static void Main()
   {
     Dog myDog = new Dog("Labrador", 1);    // myDog points to a position in memory.
     Dog yourDog = new Dog("Doberman", 3);  // yourDog points to a different position in memory.

     yourDog = myDog; // both now point to the same position in memory, 
                    // where a Dog type has values of "Labrador" and 1
   
     yourDog.Breed = "Mutt";
     myDog.Age = 13; 

     Console.WriteLine("Your dog {0}\nMy dog {1}", yourDog.ToString(), myDog.ToString());
   }
}

Since the yourDog variable and the the myDog variable both point to the same memory store, the output of which would be:

Your dog is a Mutt that is 13 years old.
My dog is a Mutt that is 13 years old.

As a practice for manipulating reference types you may want to work with the String and StringBuilder classes. We have put these with the text manipulation section but manipulating strings is a basic operation of almost all programs.

Using and building arrays edit

See MSDN for reference information.

Using classes edit

Building a custom class edit

Using interfaces edit

Building a custom interface edit

Using attributes edit

Using generic types edit

The use of the four major categories of System Generic Types will mainly be demonstrated elsewhere in this book:

  • The nullable type was discussed above
  • A whole section follows on Generic collections
  • The generic event handler will be discussed in the Event / Delegate section.
  • The generic delegates will also be discussed in the Event / Delegate section as well as in the Generic collections section (Comparer class).

If you copy the next very simple example in Visual Studio and try to add something other than an int to the list the program will not compile. This demonstrates the strong typing capability of generics.

Simple use of generics (C#)

Very simple use of generic

   using System;
   using System.Collections.Generic;
   namespace GenericsLab01
   {
       class Program
       {
           static void Main(string[] args)
           {
               List<int> myIntList = new List<int>();
               myIntList.Add(32);
               myIntList.Add(10); // Try to add something other than an int 
                                  // ex. myIntList.Add(12.5);
               foreach (int i in myIntList)
               {
                   Console.WriteLine("Item: " + i.ToString());
               }
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
       }
   }

You can use List<string> instead of List<int> and you will get a list of strings for the same price (you are using the same List(T) class).

Building generics edit

The programming of a custom generic collection was shown in the article mentioned in the topics discussion.

Here we have an example of a Generic Function. We use the trivial problem of swapping two references. Although very simple we still see the basic benefits of Generics:

  • We don't have to recode a swap function for every type
  • The generalization does not cost us the strong typing (try swapping an int and a string, it wont compile)
Simple custom generic function (C#)

Simple custom generic function

   using System;
   using System.Collections.Generic;
   using System.Text;
   namespace GenericsLab03
   {
       class Program
       {
           static void Main(string[] args)
           {
               Program pgm = new Program();
               // Swap strings
               string str1 = "First string";
               string str2 = "Second string";
               pgm.swap<string>(ref str1, ref str2);
               Console.WriteLine(str1);
               Console.WriteLine(str2);
               // Swap integers
               int int1 = 1;
               int int2 = 2;
               pgm.swap<int>(ref int1, ref int2);
               Console.WriteLine(int1);
               Console.WriteLine(int2);
               // Finish with wait
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
           // Swapping references
           void swap<T>(ref T r1,ref T r2)
           {
               T r3 = r1;
               r1 = r2;
               r2 = r3;
           }
       }
   }


Next step is to present an example including a generic interface, a generic class that implements that generic interface and a class derived from that generic class. The sample also uses interface and derivation constraints.

This is another simple problem involving employees and suppliers which have nothing in common except that they can request payment to a "payment handler" (see visitor pattern).

The problem is to know where to put the logic if you have specific processing to do for a certain kind of payment just for employees. There are myriads of ways to solve that problem but the use of generics make the following sample clean, explicit and strongly typed.

The other nice thing is that it has nothing to do with containers or collections where you will find almost all of generic samples.

Please note that the EmployeeCheckPayment<T> class derives from CheckPayment<T> giving a stronger constraint on the type parameter T (must be employee not just implement IPaymentInfo). That gives us the to opportunity to have access (in its RequestPayment method) to all payment logic (from the base class) at the same time as all employee public interface (thru the sender method parameter) and that without having to do any cast.

Custom generic interface and class (C#)

Custom generic interface and class

   using System;
   using System.Collections.Generic;
   using System.Text;
   namespace GennericLab04
   {
       class Program
       {
           static void Main(string[] args)
           {
               // Pay supplier invoice
               CheckPayment<Supplier> checkS = new CheckPayment<Supplier>();
               Supplier sup = new Supplier("Micro", "Paris", checkS);
               sup.InvoicePayment();
               // Produce employee paycheck
               CheckPayment<Employee> checkE = new EmployeeCheckPayment<Employee>();
               Employee emp = new Employee("Jacques", "Montreal", "bigboss", checkE);
               emp.PayTime();
               // Wait to finish
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
       }
       // Anything that can receive a payment must implement IPaymentInfo
       public interface IPaymentInfo
       {
           string Name { get;}
           string Address { get;}
       }
       // All payment handlers must implement IPaymentHandler
       public interface IPaymentHandler<T> where T:IPaymentInfo 
       {
           void RequestPayment(T sender, double amount);
       }
       // Suppliers can receive payments thru their payment handler (which is given by an  object factory)
       public class Supplier : IPaymentInfo
       {
           string _name;
           string _address;
           IPaymentHandler<Supplier> _handler;
           public Supplier(string name, string address, IPaymentHandler<Supplier> handler)
           {
               _name = name;
               _address = address;
               _handler = handler;
           }
           public string Name { get { return _name; } }
           public string Address { get { return _address; } }
           public void InvoicePayment()
           {
               _handler.RequestPayment(this, 4321.45);
           }
       }
       // Employees can also receive payments thru their payment handler (which is given by an  object factory)
       // even if they are totally distinct from Suppliers
       public class Employee : IPaymentInfo
       {
           string _name;
           string _address;
           string _boss;
           IPaymentHandler<Employee> _handler;
           public Employee(string name, string address, string boss, IPaymentHandler<Employee> handler)
           {
               _name = name;
               _address = address;
               _boss = boss;
               _handler = handler;
           }
           public string Name { get { return _name; } }
           public string Address { get { return _address; } }
           public string Boss { get { return _boss; } }
           public void PayTime()
           {
               _handler.RequestPayment(this, 1234.50);
           }
       }
       // Basic payment handler
       public class CheckPayment<T>  : IPaymentHandler<T> where T:IPaymentInfo
       {
           public virtual void RequestPayment (T sender, double amount) 
           {
               Console.WriteLine(sender.Name);
           }
       }
       // Payment Handler for employees with supplementary logic
       public class EmployeeCheckPayment<T> : CheckPayment<T> where T:Employee
       {
           public override void RequestPayment(T sender, double amount)
           {
               Console.WriteLine("Get authorization from boss before paying, boss is: " + sender.Boss);
	            base.RequestPayment(sender, amount);
           }
       }
   }

Exception classes edit

Some links to MSDN:

  • Exceptions and exception handling - MSDN
  • Handling and throwing exceptions - MSDN
  • Exception Hierarchy - MSDN
  • Exception Class and Properties - MSDN

Boxing and unboxing edit

See MSDN

All types derive directly or indirectly from System.Object (including value types by the way of System.ValueType). This allows the very convenient concept of a reference to "any" object but poses some technical concerns because value types are not "referenced". Comes boxing and unboxing.

Boxing and unboxing enable value types to be treated as objects. Boxing a value type packages it inside an instance of the Object reference type. This allows the value type to be stored on the garbage collected heap. Unboxing extracts the value type from the object. In this example, the integer variable i is boxed and assigned to object o:

int i = 123;
object o = (object) i;  // boxing

Please also note that it is not necessary to explicitly cast an integer to an object (as shown in the example above) to cause the integer to be boxed. Invoking any of its methods would also cause it to be boxed on the heap (because only the boxed form of the object has a pointer to a virtual method table):

int i=123;
String s=i.toString(); //This call will cause boxing

There is also a third way in which a value type can be boxed. That happens when you pass a value type as a parameter to a function that expects an object. Let's say there is a function prototyped as:

void aFunction(object value)

Now let's say from some other part of your program you call this function like this:

int i=123;
aFunction(i); //i is automatically boxed

This call would automatically cast the integer to an object, thus resulting in boxing.

The object o can then be unboxed and assigned to integer variable i:

o = 123;
i = (int) o;  // unboxing

Performance of boxing and unboxing

In relation to simple assignments, boxing and unboxing are computationally expensive processes. When a value type is boxed, an entirely new object must be allocated and constructed. To a lesser degree, the cast required for unboxing is also expensive computationally.

TypeForwardedToAttribute Class edit

See MSDN

For a discussion of TypeForwardToAttribute in the CLR see MSDN
Other possible links: Marcus' Blog, NotGartner