简单复习一下 C# 基础语法(第六期)。
委托
委托是一个类,可以被实例化,包装一系列的方法,相当于一个方法的容器,可以作为参数传入其他方法。
模板
方法的参数列表内,有一个委托参数,方法体会调用这个委托参数。
而委托参数包装的是什么样的方法,则由外部传入的方法决定。
相当于一个填空题,借助外部的方法产生结果。
模板委托方法通常在代码的中间部分,有返回值。
定义一个 Person 类,包含人物的名字和正在思考的内容。
同时,定义一个 Answer 类,用来包装人物答题的内容。
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
| public class Person { public string person; public string result;
public Person(string name, string thinking) { person = name; result = thinking; }
public Answer Think() { Answer answer = new Answer(); answer.person = person; answer.result = result; return answer; } }
public class Answer { public string person; public string result; }
|
再定义一个 Exam 类,输出答题人和答案。
其中,方法的参数是一个委托,Func 类是 C# 内部定义好的一个委托类,它可以有返回值,尖括号 < > 内填入返回值的类型。
在方法内部调用委托,得到 Answer 类型的返回值,把内容打印输出。
1 2 3 4 5 6 7 8
| public class Exam { public void AnswerQuestion(Func<Answer> solution) { Answer answer = solution.Invoke(); Console.WriteLine($"答题人:{answer.person} 答案:{answer.result}"); } }
|
实际使用时,可以定义 Func 变量,new 一个委托实例,将一个实例的方法作为参数传入。
然后把委托变量传给 AnswerQuestion 方法,同一个方法,可以接收不同的委托变量,实现功能的复用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class Program { static void Main() { Exam exam = new Exam(); Person person1 = new Person("小明", "鸡排"); Person person2 = new Person("小红", "奶茶");
Func<Answer> func1 = new Func<Answer>(person1.Think); Func<Answer> func2 = new Func<Answer>(person2.Think);
exam.AnswerQuestion(func1); exam.AnswerQuestion(func2); } }
|
回调
同样,方法的参数列表内,有一个委托参数。
在执行完一系列代码之后,调用外部方法进行回调。
回调委托方法通常在代码的末尾部分,无返回值。
继续上面的例子,添加一个 Logger 类,用于答题后的信息提示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Logger { public string message;
public Logger(string message) { this.message = message; }
public void Log() { Console.WriteLine(message); } }
|
扩展 AnswerQuestion 的参数列表,并在打印答题结果之后进行回调。
Action 类也是 C# 内部定义好的一个委托类,无返回值。
1 2 3 4 5 6 7 8 9
| public class Exam { public void AnswerQuestion(Func<Answer> solution, Action callback) { Answer answer = solution.Invoke(); Console.WriteLine($"答题人:{answer.person} 答案:{answer.result}"); callback.Invoke(); } }
|
实例化 Logger 类,填入信息提示。
可以定义 Action 变量并实例化,传入 Logger 类的 Log 方法。
然后把 action 变量作为参数,作为回调方法传入 AnswerQuestion 方法中。
现在每次打印答题结果之后,都会有答题成功的信息提示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class Program { static void Main() { Exam exam = new Exam(); Person person1 = new Person("小明", "鸡排"); Person person2 = new Person("小红", "奶茶");
Func<Answer> func1 = new Func<Answer>(person1.Think); Func<Answer> func2 = new Func<Answer>(person2.Think);
Logger logger = new Logger("答题成功"); Action action = new Action(logger.Log);
exam.AnswerQuestion(func1, action); exam.AnswerQuestion(func2, action); } }
|
多播
委托是一对多的,也就是说,可以包装多个方法,当委托被调用时,会有多个方法被同时调用,调用的顺序则是方法被填入委托变量时的顺序。
在上述的例子进行扩展,再实例化一个 Logger,会提示【请继续答题】,并且使用 += 符号给 action 变量再添加一个方法引用。
此时回调方法会输出两行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class Program { static void Main() {
Logger logger1 = new Logger("答题成功"); Logger logger2 = new Logger("请继续答题"); Action action = new Action(logger1.Log); action += logger2.Log;
exam.AnswerQuestion(func1, action); exam.AnswerQuestion(func2, action); } }
|
事件
事件是在委托的基础上,对委托的访问进行限制,事件的右侧只能是 += 或 -= 符号,不能被 = 符号直接覆盖,也不能被 .Invoke 调用。
接着上述的例子,自定义一个 SubmitEventHandler 委托,和一个 SubmitSystem 类。
SubmitSystem 类包含一个委托字段,和一个事件。
事件必须同时包含 add 和 remove,对委托字段进行添加方法和移除方法。
同时,因为委托字段是私有的,外部无法调用,事件也不能被调用,所以提供一个方法 TriggerSubmit 对委托字段进行调用。
注意:如果 submitEventHandler 没有包含任何方法,则会是 null,需要进行判空。
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
| public delegate void SubmitEventHandler();
public class SubmitSystem { private SubmitEventHandler submitEventHandler;
public event SubmitEventHandler OnSubmit { add { submitEventHandler += value; } remove { submitEventHandler -= value; } }
public void TriggerSubmit() { if (submitEventHandler != null) { submitEventHandler.Invoke(); } } }
|
现在,继续添加主程序的逻辑。
实例化 submitSystem 变量,给它的事件添加两个方法,并通过 TriggerSubmit 调用被事件保护起来的私有委托。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class Program { static void Main() { Logger logger3 = new Logger("小明提交了试卷"); Logger logger4 = new Logger("小红提交了试卷"); SubmitSystem submitSystem = new SubmitSystem(); submitSystem.OnSubmit += logger3.Log; submitSystem.OnSubmit += logger4.Log; submitSystem.TriggerSubmit(); } }
|
上述事件的定义是一个完整定义,实际上可以简化。
可以直接定义事件,无需定义私有委托字段,也无需为事件添加 add 和 remove 访问器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public delegate void SubmitEventHandler();
public class SubmitSystem { public event SubmitEventHandler OnSubmit;
public void TriggerSubmit() { if (OnSubmit != null) { OnSubmit.Invoke(); } } }
|
此时,事件的右侧似乎可以被 .Invoke 调用,还可以判空。
实际上,因为 C# 会在编译时自动生成私有委托字段,但是编写代码时并没有私有委托字段,所以不得不使用事件来触发调用,不过外部对事件的访问依然只能是 += 和 -= 符号。