C#
Programming
+
、+=
、-
、-=
,將方法加入至委派的清單中null
即可22/01/19
補充: 可以使用=
賦值運算子,使用的話跟賦值一樣會把前一個值蓋過去。
/*
* delegate (回傳型別) [委派類別] (參數型別..)
* 宣告一個委派類別 - ArithmeticExpr
* 傳入方法(method)的回傳型別為void,參數型別為兩個int
*/
public delegate void ArithmeticExpr(int a, int b);
// 顯示出該方法的名稱與 a + b 的結果
public static void Add(int a, int b)
{
Console.WriteLine("Method:[Add] has been called.\nThe answer is: " + (a + b).ToString() + "\n");
}
// 顯示出該方法的名稱與 a - b 的結果
public static void Sub(int a, int b)
{
Console.WriteLine("Method:[Sub] has been called.\nThe answer is: " + (a - b).ToString() + "\n");
}
// 顯示出該方法的名稱與 a * b 的結果
public static void Multiply(int a, int b)
{
Console.WriteLine("Method:[Multiply] has been called.\nThe answer is: " + (a * b).ToString() + "\n");
}
static void Main(string[] args)
{
// 宣告三個委派
ArithmeticExpr ar1, ar2, ar3;
/*******************Section 1*******************/
ar1 = Add; // 起始將Add指派給ar1
ar1(10, 7); // Invoke委派,呼叫Add並顯示出答案: 17
ar1 = Sub; // ar1指派為Sub
ar1(10, 7); // Invoke委派,呼叫Sub並顯示出答案: 3
ar1.Invoke(10, 7); // 或是可以使用Invoke方法
/*******************Section 2*******************/
ar2 = Add; // 起始將Add指派給ar2
ar2(3, 41); // Invoke委派,呼叫Add並顯示出答案: 44
ar2 += Sub; // 將Sub方法加入ar2的方法清單中
// 現在方法清單中有Add和Sub (順序為: Add -> Sub)
ar2(3, 41); // Invoke委派,首先呼叫Add,答案為44;而後呼叫Sub,答案為-38
/*******************Section 3*******************/
ar3 = Multiply; // 起始將Multiply指派給ar3
ar3 += Add; // 將Add方法加入ar3的方法清單中
// 現在方法清單中有Multiply和Add (順序為: Multiply -> Add)
ar3 += Sub; // 將Sub方法加入ar3的方法清單中
// 現在方法清單中有Multiply、Add和Sub (順序為: Multiply -> Add -> Sub)
ar3(67, 19); // Invoke委派,依序呼叫 Multiply、Add和Sub
ar3 -= ar2; // 將Add、Sub移出方法清單
ar3(67, 19); // 再次Invoke委派,這次只有ar3被呼叫
ar3 = null; // 清空整個方法清單
ar3(18, 0); // ERROR ==> 方法清單已經被清空了,拋出例外
Console.ReadKey();
}
這個方法目前不知道可以應用在哪裡,但可以這樣使用。
使用System.MulticastDelegate.GetInvocationList()
可以獲得委派的方法清單,下列的Code可以將ar2
的方法清單內方法印出來
Delegate[] delArray = ar2.GetInvocationList();
for (int i = 0; i < delArray.Length; i++)
{
Console.WriteLine("No.{0} - Method: {1}", i + 1, delArray[i].Method.Name);
}
Null
Check 該功能至少需要有C# 6
複製基本使用最後的一段code(Line: 54 ~ 55)
...
ar3 = null; // 清空整個方法清單
ar3(18, 0); // ERROR ==> 方法清單已經被清空了,拋出例外
...
這個地方如果直接這樣使用會丟出例外,所以呼叫前會先進行null check
ar3 = null; // 清空整個方法清單
if (ar3 == null) // SAFE!
{
ar3(18, 0); // ERROR ==> 方法清單已經被清空了,拋出例外
}
C#6之後可以有更簡潔的寫法
ar3 = null; // 清空整個方法清單
ar3?.Invoke(18, 0); // SAFE! 呼叫invoke前,已經先檢查是否為null
關於這個寫法?.
稱作Null 條件運算子
+=
和-=
來註冊或取消註冊事件。注意: 當註冊該事件的類別或物件毀滅時,請務必許消註冊事件。
例子:
Main
class Program
{
static void Main(string[] args)
{
ObjectA objectA = new ObjectA("Kawaii", 5);
ObjectB objectB = new ObjectB();
ObjectC objectC = new ObjectC();
objectA.ObjAnounceEvent += objectB.ObjBGreeting;
objectA.ObjAnounceEvent += objectC.ObjCGreeting;
while (Console.ReadKey().Key == ConsoleKey.Spacebar)
{
objectA.OnObjAEventBeenCalled();
}
}
}
Object A
class ObjectA
{
public string Name { get; set; }
public int CallCountThreshold { get; set; }
private int _callCount;
public delegate void ObjADelegate(ObjectA objectA);
public event ObjADelegate ObjAnounceEvent;
public ObjectA(string name, int callCountThreshold)
{
Name = name;
CallCountThreshold = callCountThreshold;
_callCount = 0;
}
public void OnObjAEventBeenCalled()
{
_callCount += 1;
if (_callCount > CallCountThreshold)
{
ObjAnounceEvent = null;
Console.WriteLine("Clear the invocation list.");
Environment.Exit(0);
}
ObjAnounceEvent?.Invoke(this);
}
}
Object B
class ObjectB
{
public void ObjBGreeting(ObjectA objectA)
{
Console.WriteLine("Hello, " + objectA.Name + ". This is Object B.");
}
}
Object C
class ObjectC
{
public void ObjCGreeting(ObjectA objectA)
{
Console.WriteLine("I love you, " + objectA.Name + ". This is Object C.");
}
}
.NET中有定義泛型的委派類別
System.Action: 回傳值為void,可以給予0到16不等的參數值。
System.Func: 如果需要回傳值可以用Func,跟Action一樣,可以給予0到16不等的參數值,最後一個參數為回傳值的型別。
例如在上面介紹委派的例子可以這樣改寫:
// 使用內建的Func進行改寫,最後一個泛型類別為回傳值
public System.Action<int, int> ArithmeticExpr;
// 顯示出該方法的名稱與 a + b 的結果
public static void Add(int a, int b)
{
Console.WriteLine("Method:[Add] has been called.\nThe answer is: " + (a + b).ToString() + "\n");
}
// 顯示出該方法的名稱與 a - b 的結果
public static void Sub(int a, int b)
{
Console.WriteLine("Method:[Sub] has been called.\nThe answer is: " + (a - b).ToString() + "\n");
}
// 顯示出該方法的名稱與 a * b 的結果
public static void Multiply(int a, int b)
{
Console.WriteLine("Method:[Multiply] has been called.\nThe answer is: " + (a * b).ToString() + "\n");
}
System.EventHandler: 內部定義兩種EventHandler
public delegate void EventHandler(object sender, EventArgs e);
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
功能:第一種的EventHandler是不帶事件資料的,第二種可以帶我們自定義的事件資料。
首先來談談參數列(args),兩種EventHandler都有名叫sender
的參數,顧名思義,該參數指的是發起事件的物件或類別。e
則是需要傳遞的事件資料。
這樣寫的意義在於統一了事件委派的規則,讓程式碼易讀且好維護。
在傳遞事件的資料時,可以創建一個class
繼承EventArgs
,這樣就可把我們想傳遞的訊息往創建的class
裏頭塞了。
問題來了,如果沒有想傳遞的事件資料呢? 這時可以使用System.EventArgs.Empty
,這樣已表示沒有需要傳遞的事件資料了。
接下來,我們依照上面給的情境題進行改寫 :
Main
class Program
{
static void Main(string[] args)
{
ObjectA objectA = new ObjectA("Kawaii", 5);
ObjectB objectB = new ObjectB();
ObjectC objectC = new ObjectC();
objectA.ObjAnounceEvent += objectB.ObjBGreeting;
objectA.ObjAnounceEvent += objectC.ObjCGreeting;
while (Console.ReadKey().Key == ConsoleKey.Spacebar)
{
objectA.OnObjAEventBeenCalled();
}
}
}
Object A
class ObjectA
{
public string Name { get; set; }
public int CallCountThreshold { get; set; }
public int CallCount;
public EventHandler<ObjectAEventArgs> ObjAnounceEvent;
public ObjectA(string name, int callCountThreshold)
{
Name = name;
CallCountThreshold = callCountThreshold;
CallCount = 0;
}
public void OnObjAEventBeenCalled()
{
CallCount += 1;
if (CallCount > CallCountThreshold)
{
ObjAnounceEvent = null;
Console.WriteLine("Clear the invocation list.");
Environment.Exit(0);
}
ObjAnounceEvent?.Invoke(this, new ObjectAEventArgs(Name));
}
}
Object B
class ObjectB
{
public void ObjBGreeting(object sender, ObjectAEventArgs args)
{
Console.WriteLine("Hello, " + args.Name + ". This is Object B.");
}
}
Object C
class ObjectC
{
public void ObjCGreeting(object sender, ObjectAEventArgs args)
{
Console.WriteLine("I love you, " + args.Name + ". This is Object C.");
}
}
ObjectAEventArgs
class ObjectAEventArgs : EventArgs
{
public ObjectAEventArgs(string name)
{
Name = name;
}
public string Name { get; set; }
}
2019/05/01
+=
、-=
訂閱與取消訂閱事件,可以確保不能用=
把事件清單蓋過去2022/01/19