我最早开始使用委派
是在开发游戏功能的时候
当时有个需求是需要写一个角色升级的功能
(当年是个人吃人升级的时代...所以某A角色要升级 需要吃其他角色
流程大概如下
选定A角色 -> 点击升级按钮 -> 跳出角色选择视窗 -> 选好角色 -> 按下OK升级
程序码就从 点击升级按钮的事件那边开始写 架构大概是这样
/// <summary>
/// 定义一个角色的类别
/// </summary>
public class Character
{
}
/// <summary>
/// 写一个角色升级的UI
/// </summary>
public class CharacterLevelUpUI
{
/// <summary>
/// 可以被吃的角色清单
/// </summary>
private List<Character> CharacterList { get; set; }
/// <summary>
/// 被选择要吃掉的角色清单
/// </summary>
private List<Character> SelectedCharacterList { get; set; }
public CharacterLevelUpUI(List<Character> characterList)
{
CharacterList = characterList;
}
public void ShowUI()
{
//这里当作已经选完要吃的角色了
SelectedCharacterList = new List<Character>();
}
/// <summary>
/// 这里当作按下OK钮会触发的事件
/// </summary>
public void OKBtnClick()
{
//这里角色要被吃掉
//SelectedCharacterList Delete
}
}
/// <summary>
/// 点击角色升级的按钮
/// </summary>
public static void LevelUpButtonClick()
{
var characters = new List<Character>();//可以被吃的角色清单
var ui = new CharacterLevelUpUI(characters);
ui.ShowUI();
}
static void Main()
{
//执行
LevelUpButtonClick();
}
看起来没啥问题?
过了两天 又接到一个功能要做
这次要做的事情是 要做角色冒险寻宝功能
流程大概是这样
点击冒险按钮 -> 跳出角色选择视窗 -> 选好角色 -> 按下OK派出选中的角色去冒险
喔耶~好棒棒 那就复制现有的UI稍微改一下就好~(大概有很多人会这样做吧?
/// <summary>
/// 写(复制)一个角色寻宝的UI
/// </summary>
public class CharacterTreasureHunt
{
/// <summary>
/// 可以被选择寻宝的角色清单
/// </summary>
private List<Character> CharacterList { get; set; }
/// <summary>
/// 被选择要派出寻宝的角色清单
/// </summary>
private List<Character> SelectedCharacterList { get; set; }
public CharacterTreasureHunt(List<Character> characterList)
{
CharacterList = characterList;
}
public void ShowUI()
{
//这里当作已经选完要派出的角色了
SelectedCharacterList = new List<Character>();
}
/// <summary>
/// 这里当作按下OK钮会触发的事件
/// </summary>
public void OKBtnClick()
{
//这里角色要去寻宝
//SelectedCharacterList GO TO Treasure Hunt~~
}
}
/// <summary>
/// 点击角色冒险的按钮
/// </summary>
public static void TreasureHuntButtonClick()
{
var characters = new List<Character>();//可以派出冒险的角色清单
var ui = new CharacterTreasureHunt(characters);
ui.ShowUI();
}
static void Main()
{
//执行
TreasureHuntButtonClick();
}
恩恩~ 我真是太强大了! 这样多来几个也没关系~
那就再来一个角色出战吧!
/// <summary>
/// 写(复制)一个角色出战的UI
/// </summary>
public class CharacterBattle
{
/// <summary>
/// 可以被选择出战的角色清单
/// </summary>
private List<Character> CharacterList { get; set; }
/// <summary>
/// 被选择要派出出战的角色清单
/// </summary>
private List<Character> SelectedCharacterList { get; set; }
public CharacterBattle(List<Character> characterList)
{
CharacterList = characterList;
}
public void ShowUI()
{
//这里当作已经选完要出战的角色了
SelectedCharacterList = new List<Character>();
}
/// <summary>
/// 这里当作按下OK钮会触发的事件
/// </summary>
public void OKBtnClick()
{
//这里角色要去出战
//SelectedCharacterList GO TO Fight!
}
}
/// <summary>
/// 点击出战的按钮
/// </summary>
public static void BattleButtonClick()
{
var characters = new List<Character>();//可以派出出战的角色清单
var ui = new CharacterTreasureHunt(characters);
ui.ShowUI();
}
static void Main()
{
//执行
BattleButtonClick();
}
这里大概30秒就完成了 复制 贴上 修改一下注解跟实作
於是就这样 我陆续复制了7-8个角色选择功能的UI
然後晴天霹雳的事情来了!!(登愣!!
企划想要修改UI?(谜之音:我觉得新版UI比较酷!
也就是说 我现在手上有将近10个长得一模一样的UI必须要修改
这是一件会死人的事情....
仔细思考一下 其实这些功能大部分是一样的 只有选完之後的事情不同
所以我们可以把最後选完角色的事情给参数化 也就是委派
/// <summary>
/// 写一个角色选择的UI
/// </summary>
public class CharacterSelectUI
{
/// <summary>
/// 可以被选的角色清单
/// </summary>
private List<Character> CharacterList { get; set; }
/// <summary>
/// 被选择角色清单
/// </summary>
private List<Character> SelectedCharacterList { get; set; }
private Action<List<Character>> SelectFinished { get; set; }
public CharacterSelectUI(List<Character> characterList,Action<List<Character>> selectFinished)
{
CharacterList = characterList;
SelectFinished = selectFinished;
}
public void ShowUI()
{
//这里当作已经选完角色了
SelectedCharacterList = new List<Character>();
}
/// <summary>
/// 这里当作按下OK钮会触发的事件
/// </summary>
public void OKBtnClick()
{
//你要做什麽事情我不知道 但是我把选好的角色交给你让你去决定你要作什麽
SelectFinished(SelectedCharacterList);
}
}
/// <summary>
/// 点击角色升级的按钮
/// </summary>
public static void LevelUpButtonClick()
{
var characters = new List<Character>();//可以被吃的角色清单
var ui = new CharacterSelectUI(characters,(characters) =>
{
//TODO LevelUp
});
ui.ShowUI();
}
/// <summary>
/// 点击角色冒险的按钮
/// </summary>
public static void TreasureHuntButtonClick()
{
var characters = new List<Character>();//可以派出冒险的角色清单
var ui = new CharacterSelectUI(characters, (characters) =>
{
//TODO TreasureHunt
});
ui.ShowUI();
}
/// <summary>
/// 点击出战的按钮
/// </summary>
public static void BattleButtonClick()
{
var characters = new List<Character>();//可以派出出战的角色清单
var ui = new CharacterSelectUI(characters, (characters) =>
{
//TODO Battle
});
ui.ShowUI();
}
这样把事情权责拆分 选择角色的UI就只负责选择角色 真正要做的事情由呼叫端处理就可以
於是早先复制那麽多UI是没必要的 这是委派常用的情境 -- CallBack
再随便给个CallBack委派的例子 询问视窗
跳出询问视窗 按下Yes要做某件事情,按下No要做另外一件事情
这边先借用Winform的UI
public static void Ask(string message,Action yes,Action no)
{
var result = MessageBox.Show(message, "询问", MessageBoxButtons.YesNo);
if(result == DialogResult.OK)
{
yes.Invoke();
}
else if(result == DialogResult.No)
{
no.Invoke();
}
}
static void Main()
{
Ask("你要吃鱼吗?", () => { /*开始吃鱼*/ }, () => { /*没鱼吃*/ });
Ask("你要喝茶吗?", () => { /*开始喝茶*/ }, () => { /*没茶喝*/ });
}
我只是晚餐时间有点饿了..请勿多做联想!!!
委派另外一个常用的情境 就是 event 事件
public class AlarmClock
{
public Action Alarm;
public AlarmClock(DateTime alarmTime)
{
var sleep = DateTime.Now - alarmTime;//计算要等多久
Task.Run(async () =>
{
await Task.Delay(sleep);
Alarm.Invoke();
});
}
}
static void Main()
{
//设定一个闹钟 在2021/3/8 8:00:00 会提醒
var alarmClock = new AlarmClock(new DateTime(2021,3,8,8,0,0));
alarmClock.Alarm = () =>
{
//三月八号妇女节快乐~
};
}
像上面这样写 在指定的时间到的时候 就会呼叫你指定的匿名方法//三月八号妇女节快乐~
但是这样写并不好 主要是因为 就算不是闹钟本身 也可以去执行这个Alarm 例如
static void Main()
{
//设定一个闹钟 在2021/3/8 8:00:00 会提醒
var alarmClock = new AlarmClock(new DateTime(2021,3,8,8,0,0));
alarmClock.Alarm = () =>
{
//三月八号妇女节快乐~
};
alarmClock.Alarm.Invoke();//在这里就会被呼叫
}
或者有心人(白目同事?)也可以作这种处理
static void Main()
{
//设定一个闹钟 在2021/3/8 8:00:00 会提醒
var alarmClock = new AlarmClock(new DateTime(2021,3,8,8,0,0));
alarmClock.Alarm = () =>
{
//三月八号妇女节快乐~
};
alarmClock.Alarm = null; //你指定的闹钟事件就会不见了!!!然後那天你没帮老婆买礼物就死定!
}
所以 在 event情境上 我们需要在委派前面 加上 event关键字 加上去以後 除了自身类别以外
不能将其指定为null及invoke
public class AlarmClock
{
public event Action Alarm;//前面加上event关键字
public AlarmClock(DateTime alarmTime)
{
var sleep = DateTime.Now - alarmTime;
Task.Run(async () =>
{
await Task.Delay(sleep);
Alarm.Invoke();
});
}
}
static void Main()
{
//设定一个闹钟 在2021/3/8 8:00:00 会提醒
var alarmClock = new AlarmClock(new DateTime(2021,3,8,8,0,0));
alarmClock.Alarm = () =>//error
{
//三月八号妇女节快乐~
};
alarmClock.Alarm = null; //error
}
加上event後 你会发现出现两个error 原因是 event只能使用 +=, -= 不能使用 = 来注册事件
这边开个小副本 Action +=,-=,=的差别
public static void Print5()
{
Console.WriteLine(5);
}
static void Main(string[] args)
{
Action action = () => Console.WriteLine(1);
action += () => Console.WriteLine(2);
action += () => Console.WriteLine(3);
action.Invoke();// print 1 2 3
Console.WriteLine();
action = () => Console.WriteLine(4);
action.Invoke();//print 4
Console.WriteLine();
action -= () => Console.WriteLine(4);
action.Invoke();//print 4 !!!??????
Console.WriteLine();
action += Print5;
action.Invoke();//print 4 5
Console.WriteLine();
action -= Print5;
action.Invoke();//print 4
}
委派 是可以叠加的 所以一开始我宣告 1 後续在叠加上 2 3 所以会印出 1 2 3
然後我把 4 直接指派给action 这边不是叠加 是清空 所以只会印出4
然後我把 4 给减掉 可是为什麽还是会跑出4呢?
这边就用到上一篇提到的匿名函式 虽然都是print4 但是实际上这两个匿名方法是不同的记忆体 虽然执行的事情是相同
所以 -= 不起效用
我们把匿名方法换成具名方法 Print5 就会发现 +=上去後 会印出 4 5
-=後 堆叠上的print5也会被移除
副本结束 回归正题!!
所以加上event关键字後 外面要注册事件的人 只能跟委派说 我要注册 或是注销事件
我不能去执行(Invoke) 或是 重新指派事件给委派
真正能执行 跟重新指派的 只有在AlarmClock内才可以做到
static void Main()
{
//设定一个闹钟 在2021/3/8 8:00:00 会提醒
var alarmClock = new AlarmClock(new DateTime(2021,3,8,8,0,0));
alarmClock.Alarm += () =>
{
//三月八号妇女节快乐~
};
alarmClock.Alarm += () =>
{
//这天要面试 别忘了!
};
}
所以如果这样写 指定的时间一到 就会呼叫这两个跟Alarm注册的方法
平常有写一点code的 应该多少都对巨硬的
这东西有点印象 这些都是巨硬的事件 只要注册对应的事件
就会在正确(?)的时机呼叫你注册的方法 最常看到的大概就是Winform的 Button.Click
var button = new Button();
button.Click += (s, e) =>
{
//我被点了
};
另外一个很常用到委派 但可能不知道自己正在用的 Linq(我还真有同事用Linq不知道那个是委派
看到没有?
static void Main()
{
List<int> datas = new List<int>();
// var result = datas.Where(x => x % 2 == 0).ToList();
Func<int, bool> predicate = x => x % 2 == 0;
List<int> result = new List<int>();
foreach (var data in datas)
{
if (predicate(data))
{
result.Add(data);
}
}
}
Where里面就差不多长这样 但是这还扯到 yield 所以我稍微调整过 然後省略一些安全性检查
有兴趣的可以去看原始码(巨硬已经开源了!
https://github.com/microsoft/referencesource/blob/master/System.Core/System/Linq/Enumerable.cs
所以其实Where的方法就是要你提供一个删选器 Func<int,bool>(我这个范例List的元素型别是int才会是int)
会把你List的元素一个个丢进去让你验证
你就依照喜好回传true(要)false(不要) 最後他会把筛选过的结果交还给你 是不是很方便呢?
好了! 本篇实战篇就此结束!
终於可以去吃鱼喝茶了~ 饿...
接续Day04,在确认连上Ptt後,会将页面跳转至Login页,原本Day04应该要把这些都写进去的...
tags: 2021铁人赛 React 上一篇提到台股技术面的最新收盘资讯只有股票代号,似乎少了名称...
今天不写程序,先来看看官方的机器人范例 官方范例 完整程序码:https://core.teleg...
普通变数宣告後是占用某一块记忆体空间,该空间内则存放变数资料,例如:整数变数就存放整数资料,字元变数...
《刑法》第315之1条:「无故利用『工具』或『设备』窥视、窃听」或无故以『录音』、『照相』、『录影...