TDDD49 Programmering C# och .NET Lecture 2 - 2015

advertisement
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
Download