前情提要 - 事件(Event關鍵字)
事件(Event關鍵字)
event修飾了一個委託,其實是給這個委託升級了,給他加了兩個規則:
Event(事件)
- event修飾的委託,只能在類內調用執行,類外不可調用的。(只能被類內調用執行)
- event修飾的委託,不能直接賦值,只能通過+、-增減其中蘊含的方法。(只能通過
+=、-+方式去加減所蘊含的方法)
事件中的角色
TODO: EventHandler參數包…
上節提到,使用C#內置的 EventHandler這樣委託來聲明委託對象,然後有一個問題,就是說這樣一個委託對象,它需要傳入兩個參數,一個是Object類型,一個是EvnetArgs。
Object類型就代表著:到底是誰在發出這樣的事件,那後面的EvnetArgs就是發出這樣的事件需要攜帶哪些參數,但是EvnetArgs裡面沒有任何參數,它是一個空的。
所以呢,現在就得把這個問題解決一下了:那些攻擊力、是否眩暈、是否中毒,這些參數咱還得給它,逐步的給它加回來才行,不能說是一個空白的參數包就扔過去了吧。
內置委託類型 event EventHandler
1
2
//內置委託類型 event EventHandler
public event EventHandler OnAttack;
移至定義查看EventHandler: 需傳入兩個參數object,EventArgs
1
2
//移至定義查看EventHandler: 需傳入兩個參數object,EventArgs
public delegate void EventHandler(object sender, EventArgs e);
移至定義查看EventArgs: 什麼都沒有,它是空的
問題就出在這裡
1
2
3
4
5
6
7
8
9
//移至定義查看EventArgs: 什麼都沒有,它是空的,問題就出在這裡
public class EventArgs
{
[__DynamicallyInvokable]
public static readonly EventArgs Empty = new EventArgs();
[__DynamicallyInvokable]
public EventArgs() {
}
}
前情提要Code
TODO: EventArgs參數包…
如何自定義EventArgs參數包,把原本的EventArgs替換掉呢?這就需到咱們的參數泛型了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//玩家
class Player {
public event EventHandler OnAttack;
//AOE技能
public void DoAOE() {
if (OnAttack != null) {
OnAttack(this, EventArgs.Empty);
}
}
}
//敵人
class Enemy {
//攻擊我。TODO: EventArgs參數包
public void AttackMe(object sender, EventArgs args) {
Console.WriteLine("好疼啊,敵人被攻擊了!");
}
}
internal class Program
{
static void Main(string[] args)
{
Player player = new Player();
Enemy enemy = new Enemy();
//OnAttack加進去實踐一個方法
player.OnAttack += enemy.AttackMe;
//調用AOE技能,會觸發OnAttack事件,OnAttack會調用enemy.AttackMe方法
player.DoAOE();
}
}
事件參數EventArgs (參數泛型)
- 事件被觸發的時候,可以傳送自定義的事件參數
步驟
1. 定義事件類
定義自己的事件參數包class
1
2
3
4
5
class MyEventArgs {
public string text = "";
public int number = 0;
public bool flag = false;
}
- 這裡是自定義的參數表:攻擊力、是否眩暈、是否中毒…
2. 定義事件源(使用泛型)
將EventHandler特化成傳輸咱們自己的參數包類別的委託類型
1
2
3
4
class Player {
public event EventHandler<MyEventArgs> AttackEvent;
//TODO 一堆屬性
}
- 泛型可以使用在
class類別、method方法上,也可以使用在「委託」
3. 定義事件處理方法
響應方法中,將事件參數包類型替換成我們自己的參數包類型
1
public void Test<object? sender, MyEventArgs e> {...}
程式碼:自定義事件參數EventArgs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
/*
* 事件參數泛型
* 1. 定義自己的事件參數包class
* 2. 將EventHandler特化成傳輸咱們自己的參數包類別的委託類型
* 3. 響應方法中,將事件參數包類型替換成我們自己的參數包類型
*/
namespace EventArgsTest
{
//1. 定義自己的事件參數包class
class MyArgs
{
public int Attack = 0; //攻擊力(減血)
public bool IsPoison = false; //是否中毒
public bool IsHeadache = false; //是眩暈
}
//玩家
class Player
{
//2. 將EventHandler特化成傳輸咱們自己的參數包類別的委託類型
// 加上<MyArgs>後,它就變成這樣的委託:
// public delegate void EventHandler(object o, MyArgs e)
public event EventHandler<MyArgs> OnAttack;
//AOE技能
public void DoAOE()
{
if (OnAttack != null)
{
//2.1 改成自己的參數包傳進去
MyArgs args = new MyArgs();
args.Attack = 10;
args.IsHeadache = true;
args.IsPoison = true;
OnAttack(this, args);
}
}
}
//敵人
class Enemy
{
//攻擊我
//3. 響應方法中,將事件參數包類型替換成我們自己的參數包類型MyArgs
public void AttackMe(object sender, MyArgs args)
{
Console.WriteLine("好疼啊,敵人被攻擊了!");
Console.WriteLine($"攻擊力: {args.Attack}");
Console.WriteLine($"是否中毒: {args.IsPoison}");
Console.WriteLine($"是否眩暈: {args.IsHeadache}");
}
}
internal class Program
{
static void Main(string[] args)
{
Player player = new Player(); //做一個玩家物件
Enemy enemy = new Enemy(); //做一個敵人物件
//player裡面有一個OnAttack類型的事件,
//這個事件將敵人的AttackMe這樣的一個方法 加入到這個事件包裡面
player.OnAttack += enemy.AttackMe;
//隨後調用AOE技能,AOE會觸發OnAttack的實踐,
//在觸發的時候,會構建一個我們自己的事件參數包MyArgs傳入OnAttack,
//然後敵人的AttackMe()就會收到這樣的一個事件參數包MyArgs,
//然後把事件參數包MyArgs每一個參數打印出來
player.DoAOE();
}
}
}
事件練習一
請編寫鍵盤輸入管理類:InputManager,並對外暴露OnKeyInput事件,表示用戶輸入了一個字符
- 要求:使用自定義事件,將鍵盤的字符給到訂閱者
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/*
* Input Manager 練習
* 編寫一個Input Manager的class,用來接收用戶輸入的一個字符,之後調用OnInput事件,該事件會傳遞給監聽者兩個東西(Object sender, InputArgs args),args裡面攜帶著用戶輸入的字符,
*/
namespace EventTestInputManager
{
//事件參數包
class InputArgs
{
public char input;
}
class InputManager
{
//public delegate void EventHandle(object sender, InputArgs e);
public event EventHandler<InputArgs> OnInput;
public void WaitForInput()
{
while (true) //做一個死循環
{
//1. 讀取用戶輸入的一個字符
char input = Convert.ToChar(Console.Read());
//Console.WriteLine(input);
//2. 調用OnInput事件,將這個字符傳導到監聽方法/監聽者
if (OnInput != null) {
InputArgs args = new InputArgs();
args.input = input;
OnInput(this, args); //事件的調用
}
}
}
}
internal class Program
{
//監聽事件
public static void OnKeyInput(object sender, InputArgs args) {
Console.WriteLine($"收到了OnInput事件,拿到了字符:{args.input}");
}
static void Main(string[] args)
{
InputManager im = new InputManager();
im.OnInput += OnKeyInput; //方法加入事件中
im.WaitForInput();
}
}
}
事件練習二
請編寫人類餵食寵物的過程:
- 事件源:人類,人可以發出餵食的事件
- 事件參數:餵什麼食物
- 事件訂閱者:狗/貓/熊貓
- 事件處理方法:動物們的
OnFeed方法判斷當前食物是否愛吃
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
/*
* 餵食寵物
* 模擬主人餵食寵物的過程,三個寵物:狗/貓/熊貓
* 事件源:主人(OnFeed)
* 監聽者:狗 貓 熊貓,Eat()
* 事件參數包:食物 string food
*/
namespace EventTestFeedAnimals
{
//事件參數包
class FeedArgs {
public string food = "";
}
//主人
class Master
{
public event EventHandler<FeedArgs> OnFeed; //餵食事件
public void FeedAnimals(string food)
{
if (OnFeed != null)
{
//發出事件所攜帶的參數包
FeedArgs args = new FeedArgs();
args.food = food;
//調用事件,並將發起人和它相關的參數包都丟進去
//OnFeed已經有三個方法,狗 貓 熊貓他們三個各自的Eat方法
//當OnFeed被觸發的時候,就會調用這三者的Eat方法
//然後將FeedArgs傳給這三個方法裡面
//由這三個方法裡面各自判斷他們愛不愛吃
OnFeed(this, args);
}
}
}
class Dog {
public void Eat(object sender, FeedArgs args) {
if (args.food != "肉肉") {
Console.WriteLine("狗狗:不愛吃!不愛吃!");
} else {
Console.WriteLine("狗狗:愛吃愛吃!");
}
}
}
class Cat {
public void Eat(object sender, FeedArgs args) {
if (args.food != "魚魚")
Console.WriteLine("貓貓:不愛吃!不愛吃!");
} else {
Console.WriteLine("貓貓:愛吃愛吃!");
}
}
}
class Panda {
public void Eat(object sender, FeedArgs args) {
if (args.food != "竹子") {
Console.WriteLine("熊貓:不愛吃!不愛吃!");
} else {
Console.WriteLine("熊貓:愛吃愛吃!");
}
}
}
internal class Program
{
static void Main(string[] args)
{
//事件發起者
Master master = new Master();
//事件的監聽者
Dog dog = new Dog();
Cat cat = new Cat();
Panda panda = new Panda();
//將狗/貓/熊貓的Eat方法加到事件中
master.OnFeed += dog.Eat;
master.OnFeed += cat.Eat;
master.OnFeed += panda.Eat;
//主人餵食
master.FeedAnimals("竹子");
}
}
}


