Task-based asynchronous pattern (TAP)
Example
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace AsyncBreakfast
{
class Program
{
static async Task Main(string[] args)
{
Coffee cup = PourCoffee();
Console.WriteLine("coffee is ready");
var eggsTask = FryEggsAsync(2);
var baconTask = FryBaconAsync(3);
var toastTask = MakeToastWithButterAndJamAsync(2);
var breakfastTasks = new List<Task> { eggsTask, baconTask, toastTask };
while (breakfastTasks.Count > 0)
{
Task finishedTask = await Task.WhenAny(breakfastTasks);
if (finishedTask == eggsTask)
{
Console.WriteLine("eggs are ready");
}
else if (finishedTask == baconTask)
{
Console.WriteLine("bacon is ready");
}
else if (finishedTask == toastTask)
{
Console.WriteLine("toast is ready");
}
breakfastTasks.Remove(finishedTask);
}
Juice oj = PourOJ();
Console.WriteLine("oj is ready");
Console.WriteLine("Breakfast is ready!");
}
static async Task<Toast> MakeToastWithButterAndJamAsync(int number)
{
var toast = await ToastBreadAsync(number);
ApplyButter(toast);
ApplyJam(toast);
return toast;
}
private static Juice PourOJ()
{
Console.WriteLine("Pouring orange juice");
return new Juice();
}
private static void ApplyJam(Toast toast) =>
Console.WriteLine("Putting jam on the toast");
private static void ApplyButter(Toast toast) =>
Console.WriteLine("Putting butter on the toast");
private static async Task<Toast> ToastBreadAsync(int slices)
{
for (int slice = 0; slice < slices; slice++)
{
Console.WriteLine("Putting a slice of bread in the toaster");
}
Console.WriteLine("Start toasting...");
await Task.Delay(3000);
Console.WriteLine("Remove toast from toaster");
return new Toast();
}
private static async Task<Bacon> FryBaconAsync(int slices)
{
Console.WriteLine($"putting {slices} slices of bacon in the pan");
Console.WriteLine("cooking first side of bacon...");
await Task.Delay(3000);
for (int slice = 0; slice < slices; slice++)
{
Console.WriteLine("flipping a slice of bacon");
}
Console.WriteLine("cooking the second side of bacon...");
await Task.Delay(3000);
Console.WriteLine("Put bacon on plate");
return new Bacon();
}
private static async Task<Egg> FryEggsAsync(int howMany)
{
Console.WriteLine("Warming the egg pan...");
await Task.Delay(3000);
Console.WriteLine($"cracking {howMany} eggs");
Console.WriteLine("cooking the eggs ...");
await Task.Delay(3000);
Console.WriteLine("Put eggs on plate");
return new Egg();
}
private static Coffee PourCoffee()
{
Console.WriteLine("Pouring coffee");
return new Coffee();
}
}
}
- Class variables are managed by reference
- Struct variables are managed by value
using System;
namespace LISTING_2_1_Value_and_reference_types
{
class Program
{
struct StructStore
{
public int Data { get; set; }
}
class ClassStore
{
public int Data { get; set; }
}
static void Main(string[] args)
{
StructStore xs, ys;
ys = new StructStore();
ys.Data = 99;
xs = ys;
xs.Data = 100;
Console.WriteLine("xStruct: {0}", xs.Data);
Console.WriteLine("yStruct: {0}", ys.Data);
ClassStore xc, yc;
yc = new ClassStore();
yc.Data = 99;
xc = yc;
xc.Data = 100;
Console.WriteLine("xClass: {0}", xc.Data);
Console.WriteLine("yClass: {0}", yc.Data);
Console.ReadKey();
}
}
}
Output
xStruct: 100
yStruct: 99
xClass: 100
yClass: 100
Structures can contain methods, data values, properties and can have constructors. When comparing a structure with a class (which can also contain methods, data values, properties, and constructors) there are some differences between the classes and structures:
- The constructor for a structure must initialize all the data members in the structure. Data members cannot be initialized in the structure.
- It is not possible for a structure to have a parameterless constructor. it is possible for a structure to be created by calling a parameterless constructor on the structure type (eg. array), in which case all the elements of the structure are set to the default values for that type (numeric elements are set to zero and strings are set to null).
- It is not possible to create a structure by extending a parent structure object.
Enumerated types are used in situations where the programmer wants to specify a range of values that a given type can have. For example, in the computer game you may want to represent three states of an alien: sleeping, attacking, or destroyed. You can use an integer variable to do this and adopt the convention that: the value 0 means sleeping, 1 means attacking, and 2 means destroyed
Unless specified otherwise, an enumerated type is based on the int type and the enumerated values are numbered starting at 0. You can modify this by adding extra information to the declaration of the enum. You would do this if you want to set particular values to be used in JSON and XML files when enumerated variables are stored. The code here creates an AlienState enum that is stored in a byte type, and has the given values for sleeping, attacking, and destroyed.
enum AlienState:
byte {
Sleeping = 1,
Attacking = 2,
Destroyed = 4
};
Casting used to obtain numeric value held in an enum variable
...
Generic types are used extensively in C# collections, such as with the List and Dictionary classes. They allow you to create a List of any type of data, or a Dictionary of any type, indexed on any type.
using System;
namespace LISTING_2_6_Using_generic_types
{
class Program
{
class MyStack<T> where T:class
{
int stackTop = 0;
T[] items = new T[100];
public void Push(T item)
{
if (stackTop == items.Length)
throw new Exception("Stack full");
items[stackTop] = item;
stackTop++;
}
public T Pop()
{
if (stackTop == 0)
throw new Exception("Stack empty");
stackTop--;
return items[stackTop];
}
}
static void Main(string[] args)
{
MyStack<string> nameStack = new MyStack<string>();
nameStack.Push("Rob");
nameStack.Push("Mary");
Console.WriteLine(nameStack.Pop());
Console.WriteLine(nameStack.Pop());
Console.ReadKey();
}
}
}
- Referring to Example,
MyStack
can hold any type of data. - If you want to restrict it to only store reference types you can add a constraint on the possible types that T can represent
class MyStack<T> where T:class
Other constraints that can be used are listed below:
Constraint | Behaviour |
---|---|
where T : class | The type T must be a reference type |
where T : struct | The type T must be a value type. |
where T : new() | The type T must have a public, parameterless, constructor. Specify this constraint last if you are specifying a list of constraints |
where T : <base class> | The type T must be of type base class or derive from base class. |
where T : <interface name> | The type T must be or implement the specified interface. You can specify multiple interfaces. |
where T : unmanaged | The type T must not be a reference type or contain any members which are reference types. |
Creating Variant Generic Interfaces
public interface IModelMapper <out T, in TU>
{
public IEnumerable<T> MapMultiple(IEnumerable<TU> results, bool queryBool = false);
public T MapSingle(TU result, bool queryBool = false);
public T MapModel(TU result, bool queryBool = false);
}
- Can perform validation of parameters to ensure objects have valid info.
- Could throw an exception if object created will have invalid values
class Alien {
public int X;
public int Y;
public int Lives;
public Alien(int x, int y) {
if (x < 0 || y < 0)
throw new ArgumentOutOfRangeException("Invalid position");
X = x;
Y = y;
Lives = 3;
}
}
- This is called once before the creation of the very first instance of the class.
- A static constructor is a good place to load resources and initialize values that will be used by instances of the class. This can include the values of static members of the class
- A static variable is a member of a type, but it is not created for each instance of a type
- As an example of a situation where static is useful, you may decide that you want to set a maximum for the number of lives that an Alien is allowed to have. This is a value that should be stored once for all aliens. A static variable is a great place to store such a value, since it will be created once for all class instances
- To make a
static
variable constant, must useconst
keyword orreadonly
keyword
class Alien {
public static int Max_Lives = 99;
public int X;
public int Y;
public int Lives;
public Alien(int x, int y, int lives) {
if (x < 0 || y < 0)
throw new Exception("Invalid position");
if (lives > Max_Lives)
throw new Exception("Invalid lives");
X = x;
Y = y;
Lives = lives;
}
}
- Provide a way in which behaviors can be added to a class without needing to extend the class itself.
- Extension methods are defined as static methods but are called by using instance method syntax.
- Their first parameter specifies which type the method operates on. The parameter is preceded by the
this
modifier. - Extension methods are only in scope when you explicitly import the namespace into your source code with a using directive.
- An extension method can never be used to replace an existing method in a class
using System;
namespace ExtensionMethods
{
public static class MyExtensions
{
public static int LineCount(this string str)
{
return str.Split(new char[] { '\n' },
StringSplitOptions.RemoveEmptyEntries).Length;
}
}
}
namespace LISTING_2_13_Extension_method
{
using ExtensionMethods;
class Program
{
static void Main(string[] args)
{
string text = @"A rocket explorer called Wright,
Once travelled much faster than light,
He set out one day,
In a relative way,
And returned on the previous night";
Console.WriteLine(text.LineCount());
Console.ReadKey();
}
}
}
static int ReadValue(
int low, // lowest allowed value
int high, // highest allowed value
string prompt // prompt for the user
) {
// method body...
}
...
x = ReadValue(low:1, high:100, prompt: "Enter your age: ");
- Parameters must be provided after all of the required ones.
static int ReadValue(
int low, // lowest allowed value
int high, // highest allowed value
string prompt = "" // prompt for the user
) {
// method body...
}
...
x = readValue(25, 100);
- Indexing mechanism to provide indexed property values
using System;
namespace LISTING_2_16_Indexed_properties
{
class IntArrayWrapper
{
// Create an array to store the values
private int[] array = new int[100];
// Decleare an indexer property
public int this[int i]
{
get { return array[i]; }
set { array[i] = value; }
}
}
class Program
{
static void Main(string[] args)
{
IntArrayWrapper x = new IntArrayWrapper();
x[0] = 99;
Console.WriteLine(x[0]);
Console.ReadKey();
}
}
}
- Only methods that have been marked as
virtual
in the parent class can be overridden. - In the child class, overriden method marked as
override
- If a another child class inherits the first child class (now a parent class of the second child class), we can use the
base
keyword in child class to call a method in the parent class
class PrePaidInvoice: Invoice {
public override void DoPrint() {
base.DoPrint();
Console.WriteLine("Hello from DoPrint in PrePaidInvoice");
}
}
- Value to reference type → boxing
- Reference to value type → unboxing -Each built-in C# value type ( int, float etc) has a matching C# type called its interface type to which it is converted when boxing is performed. Example, the interface type for int is int32.
namespace LISTING_2_21_Boxing_and_unboxing {
class Program {
static void Main(string[] args) {
// the value 99 is boxed into an object
object o = 99;
// the boxed object is unboxed back into an int
int oVal = (int) o;
Console.WriteLine(oVal);
Console.ReadKey();
}
}
}
- The System.Convert class provides a set of static methods that can be used to perform type conversion between .NET types.
Example
int myAge = Convert.ToInt32("21");
The convert method will throw an exception if the string provided cannot be converted into an integer.
- The keyword
dynamic
is used to identify items for which the C# compiler should suspend static type checking. The compiler will then generate code that works with the items as described, without doing static checking to make sure that they are valid. Note that this doesn’t mean that a program using dynamic objects will always work; if the description is incorrect the program will fail at run time - A variable declared as dynamic is allocated a type that is inferred from the context in which it is used.
Example
dynamic d = 99;
d = d + 1;
Console.WriteLine(d);
d = "Hello";
d = d + " Rob";
Console.WriteLine(d);
dynamic person = new ExpandoObject();
person.Name = "Rob Miles";
person.Age = 21;
Console.WriteLine("Name: {0} Age: {1}", person.Name, person.Age);
Console.ReadKey();
- The
ExpandoObject
class allows a program to dynamically add properties to an object. - A program can add ExpandoObject properties to an ExpandoObject to create nested data structures. An ExpandoObject can also be queried using LINQ and can exposes the IDictionary interface to allow its contents to be queried and items to be removed. ExpandoObject is especially useful when creating data structures from markup languages, for example when reading a JSON or XML document.
Example
dynamic person = new ExpandoObject();
person.Name = "Rob Miles";
person.Age = 21;
Console.WriteLine("Name: {0} Age: {1}", person.Name, person.Age);
private
,public
,protected
Example
class Customer
{
private string _nameValue;
public string Name
{
get
{
return _nameValue;
}
set
{
if (value == "")
throw new Exception("Invalid customer name");
_nameValue = value;
}
}
}
- private data member _nameValue holds the value of the name that is being managed by the property. This value is called the backing value of the property. If you just want to implement a class member as a property, but don’t want to get control when the property is accessed, you can use auto-implemented properties.
- The statement here creates an integer property called Age. The C# compiler automatically creates the backing values. If you want to add get and set behaviors and your own backing value later, you can do this.
public int Age {get; set;}
class BankAccount
{
private decimal _accountBalance = 0;
public void PayInFunds(decimal amountToPayIn)
{
_accountBalance = _accountBalance + amountToPayIn;
}
public bool WithdrawFunds(decimal amountToWithdraw)
{
if (amountToWithdraw > _accountBalance)
return false;
_accountBalance = _accountBalance - amountToWithdraw;
return true;
}
public decimal GetBalance()
{
return _accountBalance;
}
}
- Default access to a member is
private
- Must explicitly state
public
to make it visible outside the type
- The
protected
access modifier makes a class member useable in any classes that extend the parent (base) class in which the member is declared
- The
internal
access modifier will make a member of a type accessible within the assembly in which it is declared. You can regard an assembly as the output of a C# project in Visual Studio. It can be either an executable program (with the language extension .exe) or a library of classes (with the language extension .dll). Internal access is most useful when you have a large number of cooperating classes that are being used to provide a particular library component. These classes may want to share members which should not be visible to programs that use the library. Using the access modifier internal allows this level of sharing.
- The
readonly
access modifier will make a member of a type read only. The value of the member can only be set at declaration or within the constructor of the class.
- When a class implements an interface it contains methods with signatures that match the ones specified in the interface. You can use an explicit interface implementation to make methods implementing an interface only visible when the object is accessed via an interface reference.
- You can achieve this by making the implementation of the printing methods explicit, thus adding the interface name to the declaration of the method body.
Example
interface IPrintable
{
string GetPrintableText(int pageWidth, int pageHeight);
string GetTitle();
}
class Report : IPrintable
{
string IPrintable.GetPrintableText(int pageWidth, int pageHeight)
{
return "Report text to be printed";
}
string IPrintable.GetTitle()
{
return "Report title to be printed";
}
}
Interface methods not exposed
Interface methods exposed
- Sometimes a class may implement multiple interfaces, in which case it must contain all the methods defined in all the interfaces. This can lead to problems, in that two interfaces might contain a method with the same name
- Use explicit implementation to overcome this
interface IPrintable
{
string GetPrintableText(int pageWidth, int pageHeight);
string GetTitle();
}
interface IDisplay
{
string GetTitle();
}
class Report : IPrintable, IDisplay
{
string IPrintable.GetPrintableText(int pageWidth, int pageHeight)
{
return "Report text to be printed";
}
string IPrintable.GetTitle()
{
return "Report title to be printed";
}
string IDisplay.GetTitle()
{
return "Report title to be displayed";
}
}
-
An interface in a C# program specifies how a software component could be used by another software component. So, instead of starting to build an application by designing classes. you should instead be thinking about describing their interfaces (what each software component will do). How the component performs its function can be encapsulated inside the component
-
Benefit is "decoupling" of class that utilizes an object that implements an interface
Example
interface IPrintable
{
string GetPrintableText(int pageWidth, int pageHeight);
string GetTitle();0
}
class Report : IPrintable
{
public string GetPrintableText(int pageWidth, int pageHeight)
{
return "Report text";
}
public string GetTitle()
{
return "Report title";
}
}
class ConsolePrinter
{
public void PrintItem(IPrintable item)
{
Console.WriteLine(item.GetTitle());
Console.WriteLine(item.GetPrintableText(pageWidth: 80, pageHeight: 25));
}
}
Printer is decoupled from object being printed
- Consider classes in terms of they can do rather than what they are, by implementing and designing interfaces
- Create reference variables that refer to objects in terms of interfaces they implement rather than the particular type they are
IAccount account = new BankAccount ();
account.PayInFunds(50);
Console.WriteLine("Balance: " + account.GetBalance());
- A class hierarchy is used when you have an application that must manipulate items that are part of a particular group
- The
is
operator determines if the type of a given object is in a particular class hierarchy or implements a specified interface
if (x is IAccount)
Console.WriteLine("this object can be used as an account");
The as
operator takes a reference and a type and returns a reference of the given type, or
null
if the reference cannot be made to refer to the object.
IAccount y = x as IAccount;
- A cast will throw an exception if it can't be performed
- using
as
will let it return null instead
Example
Example
```C#
public interface IAccount
{
void PayInFunds(decimal amount);
bool WithdrawFunds(decimal amount);
decimal GetBalance();
}
public class BankAccount : IAccount
{
protected decimal _balance = 0;
public virtual bool WithdrawFunds(decimal amount)
{
if (_balance < amount)
{
return false;
}
_balance = _balance - amount;
return true;
}
void IAccount.PayInFunds(decimal amount)
{
_balance = _balance + amount;
}
decimal IAccount.GetBalance()
{
return _balance;
}
}
public class BabyAccount : BankAccount, IAccount
{
public override bool WithdrawFunds(decimal amount)
{
if (amount > 10)
{
return false;
}
if (_balance < amount)
{
return false;
}
_balance = _balance - amount;
return true;
}
}
class Program
{
static void Main(string[] args)
{
IAccount b = new BabyAccount();
b.PayInFunds(50);
if (b.WithdrawFunds(10))
Console.WriteLine("Withdraw succeeded");
else
Console.WriteLine("Withdraw failed");
Console.ReadKey();
}
}
- The C# compiler needs to know if a method is going to be overridden. This is because it
must call an overridden method in a slightly different way from a “normal” one. Done by assinging
virtual
keyword to method that will be overriden in child classes - The C# language does not allow the overriding of explicit implementations of interface methods.
Example
public class BabyAccount : BankAccount, IAccount
{
public override bool WithdrawFunds(decimal amount)
{
if (amount > 10)
{
return false;
}
else
{
return base.WithdrawFunds(amount);
}
}
}
- replace a method in a base class by simply creating a new method in the child class. No overriding, just supply a new version of the method.
- replacement method is not able to use
base
keyword
public class BabyAccount: BankAccount, IAccount
{
public new bool WithdrawFunds(decimal amount) {
if (amount > 10) {
return false;
}
if (_balance < amount) {
return false;
}
_balance = _balance - amount;
return true;
}
}
- You can only seal an overriding method and sealing a method does not prevent a child class from replacing a method in a parent.
- You can also mark a class as sealed. This means that the class cannot be extended, so it cannot be used as the basis for another class
public sealed class BabyAccount : CustomerAccount,IAccount
{
.....
}
- Creating a child class instance involves creating an instance of the base class aka. invokes constructor of base class
public class BabyAccount: BankAccount, IAccount
{
public BabyAccount(int initialBalance): base(initialBalance) {}
}
- Overriding can be used to force a set of behaviors on items in a class hierarchy.
abstract
keyword is used on a method or class variable in the base class, which has to be overriden in child classes
- abstract classes are different in that they can contain fully implemented methods alongside the abstract ones. This can be useful because it means you don’t have to repeatedly implement the same methods in each of the components that implement a particular interface.
- A class can only inherit from one parent, so it can only pick up the behaviors of one class.
- Some languages support multiple inheritance, where a class can inherit from multiple parents. C# does not allow this.
- Reference to a base class in a class hierarchy can refer to an instance of any of the classes that inherits from that base class.
- However, the reverse is not true. This is because the child class may have added extra behaviors to the parent class
- Preferred to manage references to objects in terms of the interfaces than the type of the particular object. This is much more flexible, in that you’re not restricted to a particular type of object when developing the code
- The
IComparable
interface is used by .NET to determine the ordering of objects when they are sorted
public interface IComparable {
//
// Summary:
// Compares the current instance with another object of the same type and
returns
// an integer that indicates whether the current instance precedes, follows, or
// occurs in the same position in the sort order as the other object.
//
// Parameters:
// obj:
// An object to compare with this instance.
//
// Returns:
// A value that indicates the relative order of the objects being compared. The
// return value has these meanings: Value Meaning Less than zero This instance
precedes
// obj in the sort order. Zero This instance occurs in the same position in the
// sort order as obj. Greater than zero This instance follows obj in the sort
order.
//
// Exceptions:
// T:System.ArgumentException:
// obj is not the same type as this instance.
int CompareTo(object obj);
}
Example
public interface IAccount
{
void PayInFunds(decimal amount);
bool WithdrawFunds(decimal amount);
decimal GetBalance();
}
public class BankAccount : IAccount, IComparable
{
private decimal balance;
public virtual bool WithdrawFunds(decimal amount)
{
if (balance < amount)
{
return false;
}
balance = balance - amount;
return true;
}
void IAccount.PayInFunds(decimal amount)
{
balance = balance + amount;
}
decimal IAccount.GetBalance()
{
return balance;
}
public int CompareTo(object obj)
{
// if we are being compared with a null object we are definitely after it
if (obj == null) return 1;
// Convert the object reference into an account reference
IAccount account = obj as IAccount;
// as generates null if the conversion fails
if (account == null)
throw new ArgumentException("Object is not an account");
// use the balance value as the basis of the comparison
return this.balance.CompareTo(account.GetBalance());
}
public BankAccount(decimal initialBalance)
{
balance = initialBalance;
}
}
class Program
{
static void Main(string[] args)
{
// Create 20 accounts with random balances
List<IAccount> accounts = new List<IAccount>();
Random rand = new Random(1);
for(int i=0; i<20; i++)
{
IAccount account = new BankAccount(rand.Next(0, 10000));
accounts.Add(account);
}
// Sort the accounts
accounts.Sort();
// Display the sorted accounts
foreach(IAccount account in accounts)
{
Console.WriteLine(account.GetBalance());
}
Console.ReadKey();
}
}
}
- Can be used to create a CompareTo that only accepts parameters of a specified type.
- Avoids casting and throwing invalid type exception at runtime
public interface IAccount: IComparable <IAccount>
{
void PayInFunds(decimal amount);
bool WithdrawFunds(decimal amount);
decimal GetBalance();
}
public class BankAccount: IAccount, IComparable < BankAccount >
{
private decimal balance;
public int CompareTo(IAccount account)
{
// if we are being compared with a null object we are definitely after it
if (account == null) return 1;
// use the balance value as the basis of the comparison
return this.balance.CompareTo(account.GetBalance());
}
}
- The
IEnumerator
interface defines the basic low-level protocol by which elements in a collection are traversed—or enumerated—in a forward-only manner. Its declaration is as follows:
public interface IEnumerator
{
bool MoveNext();
object Current { get; }
void Reset();
}
- Collections do not usually implement enumerators; instead, they provide enumerators,
via the interface
IEnumerable
:
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
IEnumerator<int>
interface means it contains a call ofGetEnumerator
to get an enumerator from itEnumerable
interface means it can be enumerated
class EnumeratorThing: IEnumerator<int> , IEnumerable {
int count;
int limit;
public int Current {
get {
return count;
}
}
object IEnumerator.Current {
get {
return count;
}
}
public EnumeratorThing(int limit) {
count = 0;
this.limit = limit;
}
public void Dispose() {}
public bool MoveNext() {
if (++count == limit)
return false;
else
return true;
}
public void Reset() {
count = 0;
}
public IEnumerator GetEnumerator() {
return this;
}
}
class Program {
static void Main(string[] args) {
EnumeratorThing e = new EnumeratorThing(10);
foreach(int i in e) {
Console.WriteLine(i);
}
Console.ReadKey();
}
}
- To make it easier to create iterators C# includes the
yield
keyword. - The keyword yield is followed by the return keyword and precedes the value to be
returned for the current iteration.
- The C# compiler generates all the
Current
andMoveNext
behaviors that make the iteration work, and also records the state of the iterator method so that the iterator method resumes at the statement following the yield statement when the next iteration is requested.
- The C# compiler generates all the
Example
class EnumeratorThing : IEnumerable<int>
{
private int limit;
public IEnumerator<int> GetEnumerator()
{
for (int i = 1; i < 10; i++)
yield return i;
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public EnumeratorThing(int limit)
{
this.limit = limit;
}
}
class Program
{
static void Main(string[] args)
{
EnumeratorThing e = new EnumeratorThing(10);
foreach (int i in e)
{
Console.WriteLine(i);
}
Console.ReadKey();
}
}
// Summary:
// Provides a mechanism for releasing unmanaged resources.
public interface IDisposable {
//
// Summary:
// Performs applicationdefined
tasks associated with freeing, releasing, or
resetting
// unmanaged resources.
void Dispose();
}
- To trigger, either call
Dispose()
directly or make use of C#using
class CrucialConnection : IDisposable
{
public void Dispose()
{
Console.WriteLine("Dispose called");
}
public void ThrowException()
{
throw new Exception("Bang");
}
}
class Program
{
static void Main(string[] args)
{
using (CrucialConnection c = new CrucialConnection())
{
// do something with the crucial connection
}
Console.ReadKey();
}
}
- An object can be made to publish events to which other objects can subscribe. Components of a solution that communicate using events in this way are described as loosely coupled. The only thing one component has to know about the other is the design of the publish and subscribe mechanism.
- NET libraries provide a number of pre-defined delegate types. One of them is the
Action
delegate - There are a number of pre-defined Action delegate types. The simplest
Action
delegate represents a reference to a method that does not return a result (the method is of type void) and doesn’t accept any parameters. You can use anAction
to create a binding point for subscribers
- Subscribers bind to a publisher by using the
+=
operator. The+=
operator is overloaded to apply between a delegate and a behavior - Delegates added to a published event are called on the same thread as the thread publishing
the event. If a delegate blocks this thread, the entire publication mechanism is blocked. This
means that a malicious or badly written subscriber has the ability to block the publication of
events. This is addressed by the publisher starting an individual task to run each of the event
subscribers. The Delegate object in a publisher exposes a method called
GetInvocationList
, which can be used to get a list of all the subscribers.
Example
class Alarm
{
// Delegate for the alarm event
public Action OnAlarmRaised { get; set; }
// Called to raise an alarm
public void RaiseAlarm()
{
// Only raise the alarm if someone has
// subscribed.
OnAlarmRaised?.Invoke();
}
}
class Program
{
// Method that must run when the alarm is raised
static void AlarmListener1()
{
Console.WriteLine("Alarm listener 1 called");
}
// Method that must run when the alarm is raised
static void AlarmListener2()
{
Console.WriteLine("Alarm listener 2 called");
}
static void Main(string[] args)
{
// Create a new alarm
Alarm alarm = new Alarm();
// Connect the two listener methods
alarm.OnAlarmRaised += AlarmListener1;
alarm.OnAlarmRaised += AlarmListener2;
alarm.RaiseAlarm();
Console.WriteLine("Alarm raised");
Console.ReadKey();
}
}
- The
-=
method is used to unsubscribe from events
class Alarm
{
// Delegate for the alarm event
public Action OnAlarmRaised { get; set; }
// Called to raise an alarm
public void RaiseAlarm()
{
// Only raise the alarm if someone has
// subscribed.
OnAlarmRaised?.Invoke();
}
}
class Program
{
// Method that must run when the alarm is raised
static void AlarmListener1()
{
Console.WriteLine("Alarm listener 1 called");
}
// Method that must run when the alarm is raised
static void AlarmListener2()
{
Console.WriteLine("Alarm listener 2 called");
}
static void Main(string[] args)
{
// Create a new alarm
Alarm alarm = new Alarm();
// Connect the two listener methods
alarm.OnAlarmRaised += AlarmListener1;
alarm.OnAlarmRaised += AlarmListener2;
alarm.RaiseAlarm();
Console.WriteLine("Alarm raised");
alarm.OnAlarmRaised -= AlarmListener1;
alarm.RaiseAlarm();
Console.WriteLine("Alarm raised");
Console.ReadKey();
}
}
- The
Func
types provide a range of delegates for methods that accept values and return results. There are versions of the Func type that accept up to 16 input items. - The add method here accepts two integers and returns an integer as the result.
Example
Func<int,int,int> add = (a, b) => a + b;
- The
Predicate
built in delegate type lets you create code that takes a value of a particular type and returns true or false.
Example
Predicate<int> dividesByThree = (i) => i % 3 == 0;
....
....