TDDD49 Programmering C# och .NET Lecture 2 - 2015 Johannes Schmidt, Department of Computer and Information Science (IDA), Linköping University In this lecture: ● More on Value types, boxing ● Generics ● Delegates, Lambda Expressions ● Using delegates to implement the observer pattern ● Events Value types vs Reference types (repetition) Value types: Reference types: A variable contains the data directly. A variable contains a reference to the data. Examples: all primitive types from last slide and structs Examples: strings, arrays, objects decimal x = 8; decimal y = 9; x = y; // 16 bytes of data are copied string a = "abc"; string b = "abracadabra"; a = b; // only a reference is copied // (typically 4-8 bytes) Value types Remember: a variable of value type directly contains its data, there is no reference to data You define custom value types with the struct keyword. Declaration as a class, with few exceptions: the default constructor (parameterless) sets ALL fields to its default values ● userdefined default constructors are not allowed ● any userdefined constructor must set ALL fields ● structs are implicitly sealed, but they can implement interfaces ● Declare a struct public struct Angle { public Angle(int degrees, int minutes, int seconds) { _Degrees = degrees; _Minutes = minutes; _Seconds = seconds; } public int Degrees Instantiate it: { Angle a = new Angle(0,0,0); get { return _Degrees; } Angle c = a.Move(1,1,1); } private int _Degrees; // ... } public Angle Move(int degrees, int minutes, int seconds) { return new Angle( Degrees + degrees, Minutes + minutes, Seconds + seconds); } Value types Recommendation: make value types immutable (if you need to modify it, create a new instance) Why? ● ● value types represent values (a value is immutable: 5 + 3 is 8, a new value, you do not change 5 or 3 to be 8) avoid confusion: a change in one value would not affect the content of a previously taken copy, (with a reference type it would) Boxing How to convert between a value type and a reference type? // value type -> reference type // boxing int a = 0; object obj = a; // the value of a is copied to a new memory location // then obj is set as reference to this new memory location // reference type -> value type // unboxing // cast to underlying type necessary int b = (int) obj; //double c = (double) obj; double c = (double)(int) obj; // a type check is done // then conversion // then another copy // does not work Subtle Boxing // 4 boxings, 3 unboxings int totalCount = 4; System.Collections.ArrayList list = new System.Collections.ArrayList(); list.Add((double)0); list.Add((double)1); for (int count = 0; count < totalCount - 1; count++) { list.Add( ((double)list[count] + (double)list[count + 1])); } foreach (double d in list) { Console.WriteLine("{0}", d); } Subtle Boxing // 4 boxings, 3 unboxings int totalCount = 4; System.Collections.ArrayList list = new System.Collections.ArrayList(); list.Add((double)0); list.Add((double)1); for (int count = 0; count < totalCount - 1; count++) { list.Add( ((double)list[count] + (double)list[count + 1])); } foreach (double d in list) { Console.WriteLine("{0}", d); } // boxing // boxing // boxing // unboxing // unboxing // unboxing // boxing Recommendation: avoid boxing/unboxing whenever possible since it costs time and memory Reference type or value type? Rule of thumb: majority of types should be classes. Take a struct if instances are small and commonly short-lived or are commonly embedded in other objects. AVOID defining a struct unless the type has all of the following characteristics: - It logically represents a single value, as primitive types (int, double, etc.) - It has an instance size under 16 bytes. - It is immutable. - It will not have to be boxed frequently. In all other cases, you should define your types as classes. See also https://msdn.microsoft.com/en-us/library/ms229017%28v=vs.110%29.aspx Generics – why? Using an ordinary stack to store Symbols: System.Collections.Stack myStack = new System.Collections.Stack(); Symbol mySymbol = new Symbol(30); myStack.Push(mySymbol); Symbol s = (Symbol) myStack.Pop(); myStack.Push("abc"); Using a generic stack to store Symbols: System.Collections.Generic.Stack<Symbol> myStack = new System.Collections.Generic.Stack<Symbol>(); Symbol mySymbol = new Symbol(30); myStack.Push(mySymbol); Symbol s = myStack.Pop(); // myStack.Push("abc"); // no cast required // does no longer work Generics – why? Benefits of Generics: increased type safety ● detect more errors during compile time ● reduce need of boxing/unboxing -> reduce memory consumption and increase performance ● not necessary to rewrite (almost identical) code for a specific type ● reduce casting -> higher performance ● increase code readability ● Subtle Boxing // 4 boxings, 3 unboxings int totalCount = 4; System.Collections.ArrayList list = new System.Collections.ArrayList(); list.Add((double)0); list.Add((double)1); for (int count = 0; count < totalCount - 1; count++) { list.Add( ((double)list[count] + (double)list[count + 1])); } foreach (double d in list) { Console.WriteLine("{0}", d); } // boxing // boxing // boxing // unboxing // unboxing // unboxing // boxing Avoid Boxing with Generics // only one boxing int totalCount = 4; System.Collections.Generic.List<double> list = new System.Collections.Generic.List<double>(); list.Add((double)0); list.Add((double)1); for (int count = 0; count < totalCount - 1; count++) { list.Add( ((double)list[count] + (double)list[count + 1])); } foreach (double d in list) { Console.WriteLine("{0}", d); } // no boxing // no boxing // no boxing // no unboxing // no unboxing // no unboxing // boxing Generics – Declaration class Tree<T> { public void Insert(T data) { // ... } public bool Present(T data) { // ... } } public T ExtractRoot() { // ... } Generic Interfaces interface IPair<T> { T First { get; set; } T Second { get; set; } } public struct Pair<T> : IPair<T> { T First { get; set; } T Second { get; set; } public override void toString() { // ... } } default(.) Remember: in a struct the constructor must initialize all fields This causes a problem with generics. What value to take, if T is unknown? Solution: default(T) works out struct yourStruct<T> { T a; public yourStruct(int a) { this.a = default(T); } } int a = default(int); string s = default(string); bool b = default(bool); object obj = default(object); // // // // 0 null false null Multiple type parameters // multiple type Parameters interface IPair<TFirst, TSecond> { //... } Generics - Constraints // sometimes a type parameter is required to have a certain functionality, // i.e., it has to implement an interface public class Tree<T> where T : System.IComparable<T> { //.... } // or the type parameter is required to be a subclass of a certain class public class myDataStructure<T> where T : myBaseType { //.... } // multiple constraints public class MyDictionary<T1, T2> : BaseDictionary where T1 : IComparable<T1>, myBaseType where T2 : myBaseType { //.... } Generic Methods public static void swap<T>(ref T a, ref T b) { T temp = a; a = b; b = a; } int a = 1; int b = 4; double c = 4.0D; double d = 2.32D; swap<int>(ref a, ref b); swap<double>(ref c, ref d); // type can also be omitted, the compiler will infer it (if possible) swap(ref a, ref b); Delegates A Delegate is a reference type that can refer to functions (methods). A certain Delegate can only refer to functions of a certain signature. The signature of a function is defined by the return type and the list of input types. e.g. public delegate bool ComparisonOperator(int value1, int value2); Delegates A Delegate is a reference type that can refer to functions (methods). A certain Delegate can only refer to functions of a certain signature. The signature of a function is defined by the return type and the list of input types. e.g. public delegate bool ComparisonOperator(int value1, int value2); public delegate int AnotherOperator(int value1, int value2); public static bool LessThan(int a, int b) { return a < b; } ComparisonOperator f; AnotherOperator g; f = LessThan; g = LessThan; // ok // does not compile Compute Min public static int computeMin(int[] values) { int curMin = values[0]; for (int i = 1; i < values.Length; i++) { if (values[i] < curMin) { curMin = values[i]; } } return curMin; } Compute Min with Delegates public static int computeMinDelegate(int[] values, ComparisonOperator cmp) { int curEx = values[0]; for (int i = 1; i < values.Length; i++) { if (cmp(values[i], curEx)) { curEx = values[i]; } } return curEx; } Compute Min with Delegates public static bool GreaterThan(int a, int b) { return a > b; } public static bool LessThan(int a, int b) { return a < b; } max = computeMinDelegate(values, GreaterThan); min = computeMinDelegate(values, LessThan); Multicast Delegates Delegates can refer to several functions/methods in the same time public delegate void Action(); // ... Action delegate1; Action delegate2; Action delegate3; delegate1 = printHello; delegate2 = printGoodBy; delegate3 = delegate1; delegate3 += delegate2; Console.WriteLine("Invoking both delegates:"); delegate3(); Output: Invoking both delegates: Hello Good by Invoking only one delegate: Good by delegate3 -= delegate1; Console.WriteLine("Invoking only one delegate:"); delegate3(); // consider predefined delegates from System.Func and System.Action Predefined generic Delegates From System.Action and System.Func Action delegates (no return value): public public public public ... delegate delegate delegate delegate void void void void Action(); Action<in T>(T arg); Action<in T1, in T2>(T arg1, T arg2); Action<in T1, in T2, in T3>(T arg1, T arg2, T arg3); Functional delegates (with return value): public delegate TResult Func<out T>(); public delegate TResult Func<in T1, out TResult>(T1 arg1); public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2); ... in and out: restrict the use of a type to input or output only Lambda expressions – statement lambda Define a function/method without giving it a name (int a, int b) => { return a < b; } min = computeMinDelegate(values, (int a, int b) => { return a < b; }); max = computeMinDelegate(values, (int a, int b) => { return a > b; }); A lambda expression compatible with a delegate type can be assigned to a delegate: // this works ComparisonOperator f = (int a, int b) => { return a < b; }; // this does not work ComparisonOperator f = (double d) => { return d; }; // does not compile Lambda expressions – simplified The type can be omitted, since it is derivable from the required delegate min = computeMinDelegate(values, (a, b) => { return a < b; }); In a lambda expression with only one parameter, even the parentheses around it can be omitted: a => { return -a; }; // change sign Parameterless statement: () => { return 55; }; // constant function Lambda expressions – expression lambdas If one single expression makes the body, the curly braces and the return can be omitted: min = computeMinDelegate(values, (a, b) => a < b); Observer pattern with (multicast) delegates Observer pattern: A Publisher has some information that Subscribers may be interested in. Example: A Thermostat (Publisher) measures a temperature and communicates it to a Cooler and a Heater (Subscribers) which take then actions, depending on the temperature. Thermostat Notifies about temperature Cooler Heater The Cooler // Subscriber 1 class Cooler { public Cooler(float temperature) { Temperature = temperature; } public float Temperature { get; set; } } public void OnTemperatureChanged(float newTemperature) { if(newTemperature > Temperature) { Console.WriteLine("Cooler: On"); } else { Console.WriteLine("Cooler: Off"); } } The Heater // Subscriber 2 class Heater { public Heater(float temperature) { Temperature = temperature; } public float Temperature { get; set; } public void OnTemperatureChanged(float newTemperature) { if(newTemperature < Temperature) { Console.WriteLine("Heater: On"); } else { Console.WriteLine("Heater: Off"); } } } The Thermostat // Publisher class Thermostat{ public Thermostat(){ CurrentTemperature = 0; } public Thermostat(float temperature){ CurrentTemperature = temperature; } public Action<float> OnTemperatureChange { get; set; } public float CurrentTemperature{ get { return _CurrentTemperature; } set { if (value != CurrentTemperature){ _CurrentTemperature = value; } } } // notify Subscribers Action<float> localOnChange = OnTemperatureChange; if (localOnChange != null){ localOnChange(value); } } private float _CurrentTemperature; Setting it up // Hooking up Publisher and Subscribers Thermostat thermostat = new Thermostat(24); Heater heater = new Heater(20); Cooler cooler = new Cooler(25); // subscribe thermostat.OnTemperatureChange += heater.OnTemperatureChanged; thermostat.OnTemperatureChange += cooler.OnTemperatureChanged; // provoke a notification thermostat.CurrentTemperature = // provoke another notification thermostat.CurrentTemperature = // here no notification occurs thermostat.CurrentTemperature = 21; and the heater to switch on 18; 18; Output: Heater: Cooler: Heater: Cooler: Off Off On Off Setting it up – risk 1 // Hooking up Publisher and Subscribers Thermostat thermostat = new Thermostat(24); Heater heater = new Heater(20); Cooler cooler = new Cooler(25); // subscribe thermostat.OnTemperatureChange += heater.OnTemperatureChanged; thermostat.OnTemperatureChange = cooler.OnTemperatureChanged; // provoke a notification thermostat.CurrentTemperature = // provoke another notification thermostat.CurrentTemperature = // here no notification occurs thermostat.CurrentTemperature = 21; and the heater to switch on 18; 18; Setting it up – risk 1 // Hooking up Publisher and Subscribers Thermostat thermostat = new Thermostat(24); Heater heater = new Heater(20); Cooler cooler = new Cooler(25); // subscribe thermostat.OnTemperatureChange += heater.OnTemperatureChanged; thermostat.OnTemperatureChange = cooler.OnTemperatureChanged; // provoke a notification thermostat.CurrentTemperature = // provoke another notification thermostat.CurrentTemperature = // here no notification occurs thermostat.CurrentTemperature = 21; and the heater to switch on 18; 18; Setting it up – risk 1 // Hooking up Publisher and Subscribers Thermostat thermostat = new Thermostat(24); Heater heater = new Heater(20); Cooler cooler = new Cooler(25); // subscribe thermostat.OnTemperatureChange += heater.OnTemperatureChanged; thermostat.OnTemperatureChange = cooler.OnTemperatureChanged; // provoke a notification thermostat.CurrentTemperature = // provoke another notification thermostat.CurrentTemperature = // here no notification occurs thermostat.CurrentTemperature = 21; and the heater to switch on 18; 18; Instead of just adding a new subscriber, we also remove all previous subscribers. All this for a simple typo. Setting it up – risk 2 // Hooking up Publisher and Subscribers Thermostat thermostat = new Thermostat(24); Heater heater = new Heater(20); Cooler cooler = new Cooler(25); // subscribe thermostat.OnTemperatureChange += heater.OnTemperatureChanged; thermostat.OnTemperatureChange += cooler.OnTemperatureChanged; // provoke a notification thermostat.CurrentTemperature = // provoke another notification thermostat.CurrentTemperature = // here no notification occurs thermostat.CurrentTemperature = 21; and the heater to switch on 18; 18; Setting it up – risk 2 // Hooking up Publisher and Subscribers Thermostat thermostat = new Thermostat(24); Heater heater = new Heater(20); Cooler cooler = new Cooler(25); // subscribe thermostat.OnTemperatureChange += heater.OnTemperatureChanged; thermostat.OnTemperatureChange += cooler.OnTemperatureChanged; // provoke a notification thermostat.CurrentTemperature = // provoke another notification thermostat.CurrentTemperature = // here no notification occurs thermostat.CurrentTemperature = 21; and the heater to switch on 18; 18; // raise the event manually thermostat.OnTemperatureChange(32); Setting it up – risk 2 // Hooking up Publisher and Subscribers Thermostat thermostat = new Thermostat(24); Heater heater = new Heater(20); Cooler cooler = new Cooler(25); // subscribe thermostat.OnTemperatureChange += heater.OnTemperatureChanged; thermostat.OnTemperatureChange += cooler.OnTemperatureChanged; // provoke a notification thermostat.CurrentTemperature = // provoke another notification thermostat.CurrentTemperature = // here no notification occurs thermostat.CurrentTemperature = 21; and the heater to switch on 18; 18; // raise the event manually thermostat.OnTemperatureChange(32); We can raise an event from outside the class where it is declared / supposed to be raised. Help: the event keyword // Publisher class Thermostat{ public Thermostat(){ CurrentTemperature = 0; } public Thermostat(float temperature){ CurrentTemperature = temperature; } public Action<float> OnTemperatureChange { get; set; } public float CurrentTemperature{ get { return _CurrentTemperature; } set { if (value != CurrentTemperature){ _CurrentTemperature = value; } } } // notify Subscribers Action<float> localOnChange = OnTemperatureChange; if (localOnChange != null){ localOnChange(value); } } private float _CurrentTemperature; Help: the event keyword // Publisher class Thermostat{ public Thermostat(){ CurrentTemperature = 0; } public Thermostat(float temperature){ CurrentTemperature = temperature; } public event Action<float> OnTemperatureChange { get; set; } public float CurrentTemperature{ get { return _CurrentTemperature; } set { if (value != CurrentTemperature){ _CurrentTemperature = value; } } } // notify Subscribers Action<float> localOnChange = OnTemperatureChange; if (localOnChange != null){ localOnChange(value); } } private float _CurrentTemperature; Effect of the event keywork // Hooking up Publisher and Subscribers Thermostat thermostat = new Thermostat(24); Heater heater = new Heater(20); Cooler cooler = new Cooler(25); // subscribe thermostat.OnTemperatureChange += heater.OnTemperatureChanged; thermostat.OnTemperatureChange += cooler.OnTemperatureChanged; // provoke a notification thermostat.CurrentTemperature = // provoke another notification thermostat.CurrentTemperature = // here no notification occurs thermostat.CurrentTemperature = 21; and the heater to switch on 18; 18; // raising the event from outside where it is declared is not possible // (compile error) //thermostat.OnTemperatureChange(32); Effect of the event keywork // Hooking up Publisher and Subscribers Thermostat thermostat = new Thermostat(24); Heater heater = new Heater(20); Cooler cooler = new Cooler(25); // subscribe thermostat.OnTemperatureChange += heater.OnTemperatureChanged; thermostat.OnTemperatureChange += cooler.OnTemperatureChanged; // provoke a notification thermostat.CurrentTemperature = // provoke another notification thermostat.CurrentTemperature = // here no notification occurs thermostat.CurrentTemperature = 21; and the heater to switch on 18; 18; // raising the event from outside where it is declared is not possible // (compile error) //thermostat.OnTemperatureChange(32); // assignment outside from where the event is declared is not possible // (compile error) //thermostat.OnTemperatureChange = cooler.OnTemperatureChanged; A Delegate's Invocation list What if a subcribed method throws an exception during its invocation by a delegate? If the programm interrupts, remaining subscribers will not be called anymore! // go manually through the invokation list foreach (Action<float> handler in thermostat.OnTemperatureChange.GetInvocationList()) { try { handler(90); } catch { //... } } Delegates do not have structural equality public delegate int FuncTypeA(int x); public delegate int FuncTypeB(int x); public static int methodX(int x) {return x;} // ... FuncTypeA Func1, Func2; FuncTypeB Func3; Func1 = methodX; Func2 = Func1; // ok // ok Func3 Func3 Func3 Func3 // // // // = = = = methodX; Func1; (FuncTypeB) Func1; Func1.Invoke; ok does not compile does not compile either workaround // another workaround, if only one function in the InvocationList Func3 = (FuncTypeB) Func1.GetInvocationList().First(); Links and Literature Essential C# 5.0 by Mark Michaelis https://msdn.microsoft.com/en-us/library/bb397687.aspx Lambda Expressions https://msdn.microsoft.com/en-us/library/512aeb7t.aspx Generics https://msdn.microsoft.com/en-us/library/ms173171.aspx Delegates https://msdn.microsoft.com/en-us/library/awbftdfh.aspx Events