Constructing Classes - continued

johnmora's picture

What are Generics?

Generics are part of the type system, in the CLR, that allow you to define type-safe data structures, without committing to actual data types. Instead of specifying the types of certain parameters or members, you allow the code that uses your generic class to specify them. This allows the consuming code to tailor your class to meet it’s needs. The .NET Framework includes several generic classes in the System.Collections.Generic namespace (ex: Dictionary, Queue, SortedDictionary and Sorted List). They are similar to their non generic counterparts in System.Collections except Generics offer improved performance and type safety. More detail of System.Collections.Generic will be discussed in the future (CH-4).

Why Use Generics?

Two of the significant advantages Generics has over the Object class are: Run time errors are reduced and Performance is improved. Run time errors are reduced because the compiler cannot detect type errors when you cast to and from the Object class. As an example, if you cast a string to an Object class and then attempt to cast that object to an integer, the compiler does not catch the error. Instead the application throws an exception at run time. Performance is improved because casting and boxing are no longer necessary when using Generics.

   1: //Create Object and Generic classes, which perform the same tasks.
   2: class Obj{
   3:     public object t;
   4:     public object u;
   5:     
   6:     public Obj(Object _t, Object _u){
   7:         t =_t;
   8:         u = _u;
   9:     }
  10: }
  11:  
  12: class Gen<T, U>{
  13:     public T t;
  14:     public U u;
  15:  
  16:     public Gen(T _t, U _u){
  17:         t = _t;
  18:         u = _u;
  19:     }
  20: }

The Object class has two members of type Object and the Gen class has two field members of type T and U. The consuming code determines the types for T and U, it could be a string, an int, a custom class or any combination of types. One limitation to creating a generic class (without constraints) is that generic code is valid only if it compiles for every possible constructed instance of the generic.  You are essentially limited to the capabilities of the base object class when writing generic code. So you can call the ToString() or GetHashCode() methods within your generic class but not the + operator or the > operator. Although you can do so while consuming the generic class because while consuming it you declare a specific type for the generic.

   1: //Consume the Object And Generic classes,
   2: //Add two strings using the Obj class.
   3: Obj oa = new Obj("Hello, ", "World!");
   4: Console.WriteLine((string)oa.t + (string)oa.u);
   5:  
   6: //Add two string using the Gen class
   7: Gen<string, string> ga = new Gen<string, string>("Hello ", "World!");
   8: Console.WriteLine(ga.t + ga.u);
   9:  
  10: //Add a double and an int using the Obj class.
  11: Obj ob = new Obj(10.120, 2005);
  12: Console.WriteLine((double)ob.t + (int)ob.u);
  13:  
  14: //Add a double and an int using the Gen class
  15: Gen<double, int> gb = new Gen<double, int>(10.125, 2005);
  16: Console.WriteLine(gb.t + gb.u);

When the above code is run in a Console application the Obj and Gen classes produce the same results. The code in the Gen class actually runs faster because it does not need to box and unbox to and from an object class (more will be discussed about boxing and unboxing in this chapter). To illustrate the type safety the Gen classes brings see the following code which contains errors shown in red:

   1: //Add a double and an int using the Gen class.
   2: Gen<double, int> gc = new Gen<double, int>(10.125, 2005);
   3: Console.WriteLine(gc.t + gc.u);
   4:  
   5: //Add a double and an int using the Obj class.
   6: Obj oc = new Obj(10.125, 2005);
   7: Console.WriteLine((int)oc.t + (int)oc.u);

The last line in the above example contains an error, oc.t is cast to an int instead of a double. The compiler will not catch this mistake, instead the error will occur at runtime when it tries to cast a double to an int. Even worse, in Visual Basic which allows narrowing conversions by default, a miscalculation occurs. This type of error is easier to fix when caught by the compiler than when caught at runtime.

How to use Constraints.

To overcome being limited to the base Object class when using Generics you can use constraints to place requirements on the types that consuming code can substitute for your generic parameter. Generics support four types of constraints:

  • Interface – Allows only types that implement specific interfaces to be used as a generic type argument.
  • Base Class – Allows only types that match or inherit from a specific base class to be used as a generic type argument.
  • Constructor – Requires types that are used as the type argument for your generic to implement a parameterless constructor.
  • Reference or Value Type – Requires types that are used as the type argument for your generic to be either a reference type or a value type.

In C# you use the where clause to apply a constraint to a generic, As an example the following generic class can be used only by types that implement the IComparable interface:

   1: class CompGen<T>
   2:     where T : IComparable{
   3:     public T t1;
   4:     public T t2;
   5:  
   6:     public CompGen(T _t1, T _t2){
   7:         t1 = _t1;
   8:         t2 = _t2;
   9:     }
  10:     
  11:     public T Max(){
  12:         if(t2.CompareTo(t1) < 0){
  13:             return t1;
  14:         }
  15:         else{
  16:             return t2;
  17:         }
  18:     }
  19: }

The above code compiles correctly but remove the “where” clause line and the compiler will return an error because the generic type “T” does not contain a definition for the CompareTo() method. By including the where clause and constraining the generic to classes that implement the IComparable interface it will guarantee the CompareTo() method will be available.

Events and what they are.

Most Applications are nonlinear, where you might have to wait for user to click a button and then respond to the client event. Server applications may require that you wait for an incoming network request. Events provide these capabilities in the .NET Framework. Objects, known as event senders, trigger events when an action takes place such as a user clicking a button. Event receivers can handle these requests and run a method, known as an event handler. Because the event sender does not know which method will handle the event, you must create a delegate to act as a pointer to the event handler.

Delegates.

Delegates are references to methods. Delegates themselves do not contain any code The signature for the delegate and the event handler must match. The following is an example of a delegate:

   1: //Delegate has no return value, accepts an object as first parameter
   2: //and a class derived from EventArgs as the second parameter.
   3: public delegate void myEventHandler(Object sender, EventArgs e);

The Object “sender” may contain information that the event handler might need. If you created a method which returned the results of a calculation using an event handler, you would store the results in the Object parameter. Then in the event handler you would cast the Object to the correct type.

Responding to an Event.

 

   1: //Create a method to respond to an event.
   2: private void button1_Click(Object sender, EventArgs e){
   3:     //Method code, Do stuff.
   4: }
   5:  
   6: //Add the event handler to indicate which method should receive the events.
   7: this.button1.Click += new System.EventHandler(this.button1_Click);
   8:  
   9: //When the event occurs, the method you specified runs.

 

Raising an Event.

 

   1: //You must do at least 3 things to raise an event.
   2: //Create a Delegate.
   3: public delegate void MyEventHandler(Object sender, EventArgs e);
   4:  
   5: //Create an event Object
   6: public event MyEventHandler MyEvent;
   7:  
   8: //Invoke the delegate within a method when you need to raise the event.
   9: EventArgs e = new EventArgs();
  10:  
  11: if(MyEvent != null){
  12:     //Invokes the delegates.
  13:         MyEvent(this, e);
  14: }

You can also derive a custom class from EventArgs if you need to pass information to the event handler.

What are Attributes?

Attributes describe a type, method or property in a way that can be queried programmatically using a technique called reflection. Common uses are:

  • Specify which security privileges a class requires.
  • Specify security priveledges to refuse in order to reduce security risk.
  • Declare capabilities, such as supporting serialization.
  • describe the assembly by providing a title, description etc.

Attribute types derive from System.Attribute base class. The following is an example of how to add assembly attributes(requires the System.Reflection namespace):

   1: //AssemblyInfo.cs - file
   2: [assembly: AssemblyTitle("Chapter One C#")]
   3: [assembly: AssemblyDescription("Chapter One Samples")]
   4: [assembly: AssemblyConfiguration("")]
   5: [assembly: AssemblyCompany("Microsoft Learning")]
   6: [assembly: AssemblyProduct("Training Kit")]
   7: [assembly: AssemblyCopyright("Copyright 2009")]
   8: [assembly: AssemblyTrademark("")]

Attributes do more than describe an assembly. They can also declare requirements or capabilities. Example – Enable a class to be serialized, to do so you must add the serializable attribute.

   1: //without the [Serializable] attribute, a class is
   2: // not serializable.
   3: [Serializable]
   4: class ShoppingCartItem{
   5: }
   6:  
   7: //The following uses attributes to declare it needs
   8: // to read the C:\Boot.ini file.
   9: using System.Security.Permissions;
  10:  
  11: [assembly:FileIOPermissionAttribute(SecurityAction.RequestMinimum,
  12:     Read=@"C:\Boot,ini")]
  13: namespace DeclarativeExample{
  14:     class Program{
  15:         static void Main(string[] args){
  16:             Console.WriteLine("Hello, World");
  17:         }
  18:     }
  19: }
  20:  
  21: //The runtime will throw an exception prior to execution
  22: //if the application lacks the specified privilege.

 

What is Type Forwarding?

Type forwarding allows you to move a type from one assembly to another and do so in a way as to not require to recompile clients which consume the assembly. You use the TypeForwardedTo attribute to implement type forwarding. After a component (assembly) ships and is being used by client applications, you can use type forwarding to move a type from the component , into another assembly and ship the updated component. The client applications will still work without being recompiled. Type forwarding only works for components referenced by existing applications.

To move a type from one class library to another:

  1. Add a TypeForwardedTo attribute to the source class library assembly.
  2. Cut the type definition from the source class library.
  3. Paste the type definition into the destination class library.
  4. Rebuild both libraries.
   1: //The following shows the attribute declaration used to
   2: //move TypeA to the DestLib class library.
   3:  
   4: using System.Runtime.CompilerServices;
   5:  
   6: [assembly:TypeForwardedTo(typeof(DestLib.TypeA))]

Powered by Drupal - Design by artinet