533570690 Advanced C# Page 1 of 33 Chapters 6 and 7 Delegates and Events Memory Management and Pointers How the runtime allocates space on the stack and the heap How garbage collection works How to use destructors and the System.IDisposable interface to ensure unmanaged resources are released correctly The syntax of using pointers in C# How to use pointers to implement high-performance stack-based arrays 533570690 Advanced C# Page 2 of 33 Background: Call Back Functions Passing Functions as parameters - general Example: int G ( int t , int f ( int x ) ) // function parameter - function return // type int function name f // parameter list - int x { // compute f ( t ) using function f and the parameter t. // return t. Return the product of this value and t return t * f ( t ) ; } call back occurs // xsquared is a function with an integer parameter x // and an integer return value here int xsquared ( int x ) { return x * x; } Y = G ( 3, xsquared ) t and // client calls function G with an integer parameter // and the function xsquared cout << G ( 3.0, xsquared ) << endl; // cout - prints output 27 Name of function is the address of the function Disadvantage: no type safety - 533570690 Advanced C# Page 3 of 33 Delegates Delegates are objects similar to classes – used to wrap methods These wrapped methods are then passed to other methods Advantage: Type Safety – also in .NET methods are typically inside an object They are typically associated with an instance Uses: Starting Threads void EntryPoint( ) { thread performs some taks } Thread NewThread = new Thread() Thread.Start(EntryPoint); // direct approach isWRONG Generic Library Classes Events 533570690 Advanced C# Page 4 of 33 Using Delegates in C# Single and multicast delegates Singe Cast delegates wrap one method – similar to defining a class Syntax examples: delegate void VoidOperation(unit x); delegate double TwoLongsOp(long first, long second); delegate string GetAString(); Note: a delegate type gives a name to a method Terminalogy: The instance of a class is referred to as an object The instance of a delegate is referred to as a delegate An instance of a given delegate can refer to any instance or static method on any object of any type, provided that the signature of the method matches the signature of the delegate. 533570690 Advanced C# Page 5 of 33 delegate namespace SimpleDelegate { delegate double DoubleOp(double x); class MainEntryPoint { instantiate delegates static void Main() methods are passed to { delegate constructor DoubleOp [] operations = { new DoubleOp(MathsOperations.MultiplyByTwo), new DoubleOp(MathsOperations.Square) }; pass delegate and value to a a method for (int i=0 ; i<operations.Length ; i++) { Console.WriteLine("Using operations[{0}]:", i); ProcessAndDisplayNumber(operations[i], 2.0); ProcessAndDisplayNumber(operations[i], 7.94); ProcessAndDisplayNumber(operations[i], 1.414); Console.WriteLine(); } Console.ReadLine(); } static void ProcessAndDisplayNumber(DoubleOp action, double value) { //Call back occurs here double result = action(value); Console.WriteLine("Value is {0}, result of operation is {1}", value, result); } } 533570690 Advanced C# Page 6 of 33 class MathsOperations { public static double MultiplyByTwo(double value) { return value*2; } method signatures must match the signature of the delegate public static double Square(double value) { return value*value; } } } OUTPUT: Using Value Value Value operations[0]: is 2, result of operation is 4 is 7.94, result of operation is 15.88 is 1.414, result of operation is 2.828 Using Value Value Value operations[1]: is 2, result of operation is 4 is 7.94, result of operation is 63.0436 is 1.414, result of operation is 1.999396 533570690 Advanced C# Page 7 of 33 Multicast Delegates It is possible for a delegate to wrap more than one method Such delegates are called multicast delegate The method return type must be of type void ( because methods can return different data) The wrapped methods are called in sequence Example: // return type void assumes delegate void DoubleOp(double value); multicast class MainEntryPoint { static void Main() instantiate delegate { DoubleOp operations = new DoubleOp(MathsOperations.MultiplyByTwo); operations += new DoubleOp(MathsOperations.Square); add another delegate operator += is overloaded ProcessAndDisplayNumber(operations, 2.0); ProcessAndDisplayNumber(operations, 7.94); ProcessAndDisplayNumber(operations, 1.414); Console.WriteLine(); Console.ReadLine(); } static void ProcessAndDisplayNumber(DoubleOp action, double value) { Console.WriteLine("\nProcessAndDisplayNumber called with value = " + value); action(value); // call back occurs here } } 533570690 Advanced C# Page 8 of 33 // Methods wrapped by delegate class MathsOperations { public static void MultiplyByTwo(double value) { double result = value*2; Console.WriteLine("Multiplying by 2: {0} gives {1}", value, result); } public static void Square(double value) { double result = value*value; Console.WriteLine("Squaring: {0} gives {1}", value, result); } } } OUTPUT: ProcessAndDisplayNumber called with value = 2 Multiplying by 2: 2 gives 4 Squaring: 2 gives 4 ProcessAndDisplayNumber called with value = 7.94 Multiplying by 2: 7.94 gives 15.88 Squaring: 7.94 gives 63.0436 ProcessAndDisplayNumber called with value = 1.414 Multiplying by 2: 1.414 gives 2.828 Squaring: 1.414 gives 1.999396 533570690 Advanced C# Page 9 of 33 EVENTS A form of communications between objects Windows Applications are message based A detailed example - Structure of a sample application: Consumer: MessageDisplayer 3 Event Generator: UserInputMonitor 1 UserRequestEventArgs 2 Event Handler: UserRequestHandler Event: OnUserRequest Consumer: ManagersStaffMonitor 4 UserRequestEventArgs Event Handler: UserRequestHandler Main Program Note: classic object oriented design - the main program does nothing except instantiates the various objects that make up the application Delegates are used as a means of wiring the event up when the message is received by the application The event receiver can by any application The sender’s job is to raise the event 533570690 Advanced C# Page 10 of 33 Using System; Using System.Windows.Forms; Namespace SomeNameSpace { classMainEntryPoint { static void Main(); { UserInputMonitor inputMonitor = new UserInputMonitor(); MessageDisplayer inputProcessor = new MessageDisplayer(inputMonitor); ManagersStaffMonitor Mortimer = new ManagersStaffMonitor(inputMonitor); InputMonitor.Run ( ); // start application } } } 533570690 Advanced C# Page 11 of 33 1. Define the event argument, i.e. the object that is passed to the event handler // enumerated request types enum RequestType {AdRequest, PersonalMessageRequest); class UserRequestEventArgs: EventArgs // must be derived from EventArgs { private RequestType request; // constructor public UserRequestEventArgs(RequestType request): base ( ) { this.request request; } // readonly property for private data request public RequestType Request { get { return request; } } } 533570690 Advanced C# Page 12 of 33 2. Implement UserInputMonitor – This is the object that deals with user input. It will ask the user which message they want to see class UserInputMonitor { // define delegate and method signature to call back event handlers public delegate void UserRequest(object sender, UserRequestEventArgs e) ; // tell compiler that this class contains a member event, of a type given // by the delegate public event UserRequest OnUserRequest; //method that fires event public void Run( ) { bool finished = false; do { Console.WriteLine(“Select preferred option:”); Console.WriteLine(“ Request advertisement – hit A then Return”); Console.WriteLine)” Request personal messge from Mortimer “ + “= hit P then Return”); Console.WriteLine( “ Exit = hit X then Return”); string response = Console.ReadLine(); char responseChar = (response == “ “ ? ‘ ‘ : char.ToUpper(response[0]); switch (responseChar) { case ‘X’: finished = true; break; case ‘A’: OnUserRequest(this, new UserRequestEventArgs(RequestType.AdRequest)); break; case ‘P’: OnUserRequest(this, new UserRequestEventArgs(RequestType.PersonalMessageRequest)); break; } }while (!finished); } // end of class } 533570690 Advanced C# Page 13 of 33 3. Implement MessageDisplayer – object that is responsible for displaying the appropriate message class MessageDisplayer { // constructor adds delegate – MessageDisplayer wants to be notified // so it can display the appropriate message public MessageDisplayer(UserInputMonitor monitor) { monitor.OnUserRequest += new UserInputMonitor.UserRequest(UserRequestHandler); } // Event handler protected void UserRequestHandler(object sender, UserRequestEventArgs e) // event argument { switch (e.Request) { case RequestType.AdRequest: // enumerated type Console.WriteLine(“Mortimer Phones is better than anyone “ + “else because all our software is written in C#!\n”); break; case RequestType.PersonalMessageRequest: Console.WriteLine(“Today Mortimer issued the following “ + “statement: \n Nevermore!\n”; break; } } } 533570690 Advanced C# Page 14 of 33 4. Implement ManagerStaffMonitor – the object that will like to be notified whenever someone has asked to see his personal message ( does not care about ad requests) class MangerStaffMonitor { // constructor adds delegate – ManagerStaffMonitor wants to be // notified so it can display the message Box public ManagerStaffMonitor(UserInputMonitor monitor) { monitor.OnUserRequest += new UserInputMonitor.UserRequest(UserRequestHandler); } // Event handler protected void UserRequestHandler(object sender, UserRequestEventArgs e) // event argument { if ( e.Requst == RequestType.PersonalMessageRequest) { MessageBox.Show(“Kaark!”, “Mortimer says …”); } } Sample Output given input A and P : Select preferred option: Request advertisement – hit A then Return Request personal message from Mortimer – hit P then Return Exit – hit X then Return A Mortimer Phones is better than anyone else because all our software is written in C#! Select preferred option: Request advertisement – hit A then Return Request personal message from Mortimer – hit P then Return Exit – hit X then Return P Today Mortimer issued the following statement: Nevermore! Message Box Mortimer says … Kaark! OK 533570690 Advanced C# Page 15 of 33 Generating Events using System; using System.IO; using System.ComponentModel; namespace SimpleEvent { /// <summary> /// Summary description for BusEntity. /// </summary> public class BusEntity { string _time = ""; public BusEntity() { // Adds event handler - passing method to handle the event Form1.Action += new Form1.ActionEventHandler(EventManager_Action); } private void EventManager_Action(object sender, ActionCancelEventArgs ev) { ev.Cancel = !DoActions(); // change text in event args ev object if(ev.Cancel) ev.Message = "Wasn't the right time."; } private bool DoActions() { bool retVal = false; DateTime tm = DateTime.Now; if(tm.Second < 30) { _time = "The time is " + DateTime.Now.ToLongTimeString(); retVal = true; } else _time = ""; return retVal; } public string TimeString { get {return _time;} } 533570690 Advanced C# } } Page 16 of 33 533570690 Advanced C# Page 17 of 33 using System; using System.IO; using System.ComponentModel; namespace SimpleEvent { /// <summary> /// Summary description for BusEntity. /// </summary> public class BusEntity { string _time = ""; public BusEntity() { // Adds event handler - passing method to handle the event Form1.Action += new Form1.ActionEventHandler(EventManager_Action); } private void EventManager_Action(object sender, ActionCancelEventArgs ev) { ev.Cancel = !DoActions(); // change text in event args ev object if(ev.Cancel) ev.Message = "Wasn't the right time."; } private bool DoActions() { bool retVal = false; DateTime tm = DateTime.Now; if(tm.Second < 30) { _time = "The time is " + DateTime.Now.ToLongTimeString(); retVal = true; } else _time = ""; return retVal; } public string TimeString { get {return _time;} } } } 533570690 Advanced C# Page 18 of 33 533570690 Advanced C# Page 19 of 33 Preprocessor Directives No longer important in C# - since there is no pre-processor – However, The C# compiler recognizes # directives #define and #undef - define and undefine labels #if, #elif, #else, and #endif - used to inform compiler whether or not to compile a block of code #warning and #error - raises warnings and errors when the compiler encounters them #if DEBUG && RELEASE #error “ Some message to yourself #endif #region and #endregion - directive allows blocks of code to be treated as a singe block with a given name – editors can recognize these directives and provide better code layout on the screen #line – a directive used to alter line number information that is output by the compiler in warnings and error messages ( used when compiling code together with other packages that alters the code before compilation) so that line numbers and file names match. 533570690 Advanced C# Page 20 of 33 Attributes - Attributes are defined in the .NET Framework base classes that serve as a directive to the compiler (similar to pre-processor directives) – - They can cause extra data to be provided of the compiled assembly which can be examined by using the “technique of reflection” - There is no limit to attributes since the user can define their own attributes - Attributes are markers that can be applied to a method or class Some Base .NET Class attributes: Conditional –prevent compiler from compiling the statements [Conditional (“DEBUG”)] public void DoSomeDebugStuff() { // do something } DllImport – to access basic Windows API or old-style functions – used to mark a method as being defined in an external DLL rather than in any assembly Obsolete – To mark a method that is obsolete – depending on the setting applied to the attribute, the compiler will generate a warning or an error 533570690 Advanced C# Page 21 of 33 Memory Management The fundamental type is System.Object Object Value Type Class Byte Interface Int16 UInt16 Single Double Array Char String Int32 UInt32 Int64 UInt64 Delegate Decimal Structure Others Boolean Others Others Reference Types STACK Managed Heap Enum 533570690 Advanced C# Page 22 of 33 Value Data types – using the Sytem Stack: - virtual memory – memory addressing handled by the OS – mapping memory address seeing by the program to actual location in hardware memory or on disk Stack Pointer – tells the address of the next free location (downward from high memory to low) – used to store value types and copy parameters during function calls Reference Data Types – use the managed heap (managed by the CLR) Successive objects are placed on the heap using a pointer to indicate the next free location When the garbage collector cleans up it moves free space to the end of the heap and updates all references for objects on the heap Instantiating reference types is now faster – offsetting the overhead encountered when the garbage collector runs 533570690 Advanced C# Page 23 of 33 Garbage Collection and Freeing Unmanaged Resources All objects are on the heap and referenced by the client code All objects are removed from memory by the garbage collector. There can be a delay between the reference going out of scope and the object being deleted. The garbage collector runs independent of the code and the collection of an object is non-deterministic. However, The garbage collector can be forced to run using System.GC.Collect( ) method. C# does support destructors. A destructor may be defined, which will be automatically invoked when the object is garbage-collected The underlying .NET architecture the method Finalize() is compiled replacing the user defined destructor. In general, a destructor slows down the garbage collection since it takes two passes by the garbage collector (GC)to destroy the object. The first one calls the destructor (Finalize()) and the second one removes the object. The destructor should only be used as a backup. When the destructor is called the unmanaged resources can be cleaned up. We know the destructor has been called by the garbage collector which will clean up the managed resources, so that the destructor only has to clean up unmanaged resources ( ex: closing a file). Since destruction on unmanaged resources is not deterministic, the destructor cannot reference managed resources anyway, so there is no point in letting a destructor free up managed resource. If resources need to be cleaned up immediately and cannot wait for the garbage collection to occur, or the objects of some class contain references to other managed objects that are large and should be cleaned up as soon as the object containing the references is no longer needed, the class should implement the IDisposable.Dispose ( ) method. If a client calls Idisposable.Dispose() then that client indicates that all resources associated with that object ( managed an unmanaged) should be cleaned up. 533570690 Advanced C# Page 24 of 33 The System.IDisposable Interface A recommended alternative to using a destructor Your class should inherit interface IDisposable class MyClass : IDisposable { // IDisposable declares a single method nameded Dispose(); // we implement this method in MyClass public void Dispose() { // implementation } } Example – Class ResourceGobbler ( uses some resources) ResourceGobbler theInstance = new ResourceGobbler(); // do some processing theInstance.Dispose(); // call the instance method Dispose to // clean up resources This code will fail if an exception occurs and should be placed in a try block ResourceGobbler theInstance = null; // ensures that Dispose is always called on theInstance try { ResourceGobbler theInstance = new ResourceGobbler(); // do some processing………. } catch { // call the instance method Dispose to clean up resources if (theInstance != null) theInstance.Dispose(); } 533570690 Advanced C# Page 25 of 33 A more effective way to accomplish this is using the keyword using ( a different context here as when using the keyword using for namespaces) The following code generates Intermediate Language IL equivalent to the try block using (ResourceGobbler theInstance = new ResourceGobbler()) { // do some processing } The instance theInstance is scoped to the block below the using statement When the variable theInstance goes out of scope the Dispose method will be called automatically even if exceptions occur If you have a try block – you can also call Dispose in the Finally Clause which always gets executed. Implementing IDisposable and a Destructor – In the example on the next page, the consumer calls Dispose() – indicating that all managed and unmanged resources need to be cleaned up If the GC calls Finalize(). I.e. the ~ResourceHolder (destructor – nothing needs to be cleaned up Note: - destructors have no return type, no parameters and no access modifier. - each class writes it’s own destructor - The difference between Close() and Dispose() Close() closes resources that can be opened again ( ex: files) Dispose () the client is finished using the resource 533570690 Advanced C# Page 26 of 33 public class ResourceHolder : IDisposable { private bool isDisposed = false; public void Dispose() { Dispose(true); // implement Dispose without parameter //call Dispose with bool true // tell GC that destructor no longer needs to be called // more efficient – unmanged objects are already cleaned up GC.SuppressFinalize(this); // don’t call destructor } protected virtual void Dispose(bool disposing)// with param { if (!isDisposed) { if (disposing) { //cleanup managed objects by calling their Dispose() methods } // cleanup unmanged objects } isDisposed = true; } ~ResourceHolder() // destructor called by GC Finalize () { Dispose (false); // call dispose with Boolean false } } // end of class 533570690 Advanced C# Page 27 of 33 Unsafe Code Pointers – Advantages: Powerful and flexible tool Performance – Backwards compatibility Debugging - Make memory addresses available to the user Desadvantages: The syntax is complex Pointers, when not used carefully can cause bugs in your code Easy to overwrite memory .NET type safety checks are bypassed 533570690 Advanced C# Page 28 of 33 unsafe Due to the risk associated with pointers - code using pointers is surrounded with the keyword unsafe unsafe int GetSomeNumber(0 { // code within method can use pointers } unsafe class SomeClass { // any method in the class can use pointers } void SomeMethod() { unsafe { // block within method can now use pointers } } // A variable itself cannot be declared unsave unsafe int *pX; // not possible Compiling unsafe code require compiler option /unsafe 533570690 Advanced C# Page 29 of 33 Pointer Syntax & address operator dereference opeator ( get the contents of this address) int x = 10; int *pX , *py; pX = &x; pY =pX; *pY = 20; Value of x 20 A pointer can be declared to any value type but not to an array or class. Pointers can only be declared and point to unmanaged data types The garbage collector handles CLR all managed types Casting pointers to integer types- Pointers are addresses stored as integers int x = 10; int *pX , *py; pX = &x; pY =pX; *pY = 20; uint y = (uint)pX; // cats pointer pX to an integer and store as an integer int *pD = (int*)y; // cast int y to an address and store as a pointer Pointers take 4 bytes, thus they can only be cast to an uint, long, or ulong Note: Integer value must be withing memory address range 64-bit processors will occupy 8 bytes – will certainly cause overflow Use checked context to avoid overflow 533570690 Advanced C# Page 30 of 33 Casting between Pointers byte aByte = 8; byte *pByte = &aByte; double pDouble = (double)pBytee; // legal – but could lead to problems Pointers can also be declared of type void void *pointerToVoid; pointerToVoid = (void*)pointerToInt; The sizeof Operator - takes the type as parameter and returns the size in bytes Can be used in built-in types and struct For struct the size will depend on the fields within a struct sizeof cannot be used on classes sizeof(sbyte) =1 Pointer Arithmetic ( connot be done on pointers of type void) Operators +, -, +=, -=, ++, and -- can be used When dereferencing contiguous memory, Scaling will depend on the pointer type - to dereference General : - Ex: P is of type T and X is added to P then the arithmetic is as follows: P + X *(sizeof(T)) 533570690 Advanced C# Pointers to Structs Work the same way as pointers to value types – except struct cannot contain a reference type struct MyStruct { public long X; public float F; } MyStruct * pStruct; MyStruct S = newMyStruct(); dereference operators *. Or -> pStruct = &S; *(pStruct).X = 4; OR pStruct->X = 4; Pointers to structure members: long *pL = &(S.X); float * pF = & ( pStruct->F) ; Page 31 of 33 533570690 Advanced C# Page 32 of 33 Pointers to Class Members – using fixed keyword Assigning a pointer to a member of a class in a regular way would produce a compiler error, because a class is a CLR managed object it is garbage collected and the memory pointed to by pointers is not garbage collected A special syntax is required that tells the garbage collector that there may be pointers pointing to members of certain class instances MyClass MyClassObj = new MyClass(); fixed ( long *pObject = &(myClassObject.X) { // object will not be moved until block exits } multiple fixed statements may be declared before the block fixed ( long *pX = &(myClassObject.X)) fixed (float *pF =&(myClassObject.F)) { } fixed ( long *pX = &(myClassObject.X)) { fixed (float *pF = &(myClassObject.F) { } } MyClass MyClassObj = new MyClass(); MyClass myClassObj2 = new MyClass(0; fixed ( long *pX = &(myClassObject.X), pX2 = &(myClassObj2.X)) { // object will not be moved until block exits } 533570690 Advanced C# Page 33 of 33 Example: Allocating an array on the stack using a pointer class MainEntryPoint { static unsafe void Main() { Console.Write(“How big an array do you want?”; string userInput = Console.ReadLine(); uint size = uint.Parse(userinput); long *pArray = stackalloc long ( (int)size); for ( int i = 0; i < size; i ++ ) pArray[i]= i * i; Store values using brackets Access values using pointer arithmetic for ( int i = 0; i < size; i ++ ) Console.WriteLine(“Element {0} = (i)”, I, + * ( pArray + i )); } }