个人觉得学习C#无比快活,简单易上手,很容易写出自己想要的程序。尤其是有其他面向对象语言基础的人更是能很快掌握C#基础,码出自己想要的代码。但是其他编程语言没有委托与事件,虽然不影响新手使用,但是自己实现类似功能要多额外的工作量。可能很多新手入门C#,如果没有面向对象基础,第一座大山就是面向对象,第二座大山就是委托与事件。有时间再写面向对象,尤记当年入门面向对象的痛苦。本文章目的是让不理解委托与事件的初学者快速上手,个人也是初学者,有错误欢迎指正。
关于委托(delegate)
有人说委托是函数指针,对于没有C++基础的人很难理解什么函数指针。在我看来委托是一类相同函数(返回值类型与参数列表一致)的链表。什么是返回值类型与参数列表一致的函数?这个很容易理解。如以下代码所示:
void showName(string name)
{
}
void showTitle(string title)
{
}
或者如下:
int plus(int x,int y){
return x + y;
}
int mult(int x, int y) {
return x * y;
}
上面说了委托是链表。然后你在声明委托的时候,要说明链表中函数的返回类型与参数列表,比如我声明返回类型为void,参数列表为string的委托。
delegate void func(string f);
//我声明的func与下面两个函数返回类型与参数列表一致
void showName(string name)
{
}
void showTitle(string title)
{
}
delegate int func2(int x, int y);
//我声明的func2与下面两个函数返回类型与参数列表一致
int plus(int x,int y){
return x + y;
}
int mult(int x, int y) {
return x * y;
}
上面提到很多“委托”这个词,其实后面还有省略的两个字“类型”。你声明委托是声明了一种类型。声明以后就可以把该类型当参数传入。
delegate void func();
void showName(func name)
{
}
以上func name并没有报错因为声明委托结束,代码中就有了func这个类型。既然有类型,那么我们就可以根据类型生成实例。比如
delegate void func();
func l;
我们定义了一个返回值为void,无参数的func类型的链表。然后声明了一个具体实例l。这里用l就是list的意思,帮助大家容易理解。然后就可以往链表里添加函数。使用符号“+=”
delegate void func(string f);
static func l;
static void showName(string name)
{
}
static void showTitle(string title)
{
}
static void Main(string[] args)
{
//这里两个函数要加static(静态的)不然需要你通过具体类来引用,委托同理。
l += showName;
l += showTitle;
}
通过以上操作,你就在函数链表l添加了showName与showTitle两个函数。这里很像长鞭炮,你把函数串联起来了。如果你调用l这个委托,就相当于点燃了引线,所有函数就会依次运行一遍。
delegate void func(string f);
static func l;
static void showName(string name)
{
Console.WriteLine("你先输出了名字,参数:"+name);
}
static void showTitle(string title)
{
Console.WriteLine("你又输出了主题,参数:"+title);
}
static void Main(string[] args)
{
l += showName;
l += showTitle;
l("一个参数");
Console.ReadKey();
}
运行结果:
当然你可以传入函数,在调用函数,效果一样。
delegate void func(string f);
static func l;
static void showName(string name)
{
Console.WriteLine("你先输出了名字,参数:"+name);
}
static void showTitle(string title)
{
Console.WriteLine("你又输出了主题,参数:"+title);
}
static void run(func l) {
l("一个参数");
}
static void Main(string[] args)
{
l += showName;
l += showTitle;
run(l);
Console.ReadKey();
}
链表模型(l的模型)
如果使用=号将清空链表,使链表中只剩最后一个等号后的函数。也可以使用-=,来清除对应的函数。这里请大家自行测试。
具有返回值的委托类型,返回值默认是最后一个函数的返回值,就是最后添加的返回值。
delegate int func(int j,int k);
static func l;
static int plus(int x,int y){
return x + y;
}
static int mult(int x, int y) {
return x * y;
}
static void Main(string[] args)
{
l += plus;
l += mult;
Console.WriteLine(l(4,5));
Console.ReadKey();
}
运行以上返回值是20,结果是最后一个函数返回值。如何获取每个函数的返回值,这里写了一个示例:
delegate int func(int j,int k);
static func l;
static int plus(int x,int y){
return x + y;
}
static int mult(int x, int y) {
return x * y;
}
static void Main(string[] args)
{
l += plus;
l += mult;
foreach(Delegate i in l.GetInvocationList()){
func k=(func)i;
Console.WriteLine(k(4,5));
}
Console.ReadKey();
}
委托就讲到这里,重新概括一下就是
- 委托是一种类型
- 委托是一种类型与返回值相同函数的链表
- 委托使用+=,=,-=三个符号操作链表里的函数
- 调用委托就会依次调用委托链表里的所有函数
- 委托返回值默认是最后一个添加的函数返回值
关于事件(event)
事件是一种特殊的委托,事件需要与类结合起来理解。
首先明确关键字event在实例化委托时使用而非定义委托时使用。
委托定义与使用
delegate void func();
static func l;
事件定义与使用
delegate void func();
static event func l;
注意关键字的位置,在实例化的时候使用event;
事件需要与类结合起来,定义事件的类才可以调用事件。
delegate void func();
class Program
{
static void Main(string[] args)
{
Person.l();//这里报错Person.l”只能出现在 += 或 -= 的左边(从类型“Person”中使用时除外)
Console.ReadKey();
}
}
class Person{
public static event func l;//这里定义事件
void Action() {
l();//这里没有报错因为这里在定义事件的类里。
}
}
之前讲过,委托就像是一串鞭炮。将函数链接与取消或者触发,可以自由使用。但是事件限定了只能由声明事件的类来触发。而链接函数的过程,可以看做一种通知。这里写一个黑人抬棺的例子帮助大家理解
using System;
using System.Data;
using System.IO;
namespace ConsoleApp3
{
delegate void func(string name);
class Program
{
static void Main(string[] args)
{
Person Hei1 = new Person("黑人小哥1");
Person Hei2 = new Person("黑人小哥2");
Person Hei3 = new Person("黑人小哥3");
Person Hei4 = new Person("黑人小哥4");
Person ming = new Person("小明");
ming.died += Hei1.taiGuan;
ming.died += Hei2.taiGuan;
ming.died += Hei3.taiGuan;
ming.died += Hei4.taiGuan;
ming.fellFull += Hei1.seeFull;
ming.fellFull += Hei2.seeFull;
ming.fellFull += Hei3.seeFull;
ming.fellFull += Hei4.seeFull;
ming.fellHungry += Hei1.seeHungry;
ming.fellHungry += Hei2.seeHungry;
ming.fellHungry += Hei3.seeHungry;
ming.fellHungry += Hei4.seeHungry;
ming.EatDonut(10);
Console.ReadKey();
}
}
class Person{
string name;
public event func died;
public event func fellFull;
public event func fellHungry;
public Person(string name) {
this.name = name;
}
public void EatDonut(int i){
if (i <= 10)
{
fellHungry(name);
}
else {
if (i <= 100)
{
fellFull(name);
}
else {
if (i > 100) {
died(name);
}
}
}
}
public void taiGuan(string name) {
Console.WriteLine(this.name+"收到"+name+"死了,开始抬棺!奏乐!!");
}
public void seeHungry(string name)
{
Console.WriteLine(this.name + "看到" + name + "还很饿");
}
public void seeFull(string name)
{
Console.WriteLine(this.name + "看到" + name + "吃饱了");
}
}
}
我们只调用了,小明吃了10个甜甜圈,然后四个黑人小哥就都看到了。
ming.EatDonut(10);
我把代码改一下,大家可以试着运行一下
using System;
using System.Data;
using System.IO;
namespace ConsoleApp3
{
delegate void func(string name);
class Program
{
static void Main(string[] args)
{
Person Hei1 = new Person("黑人小哥1");
Person Hei2 = new Person("黑人小哥2");
Person Hei3 = new Person("黑人小哥3");
Person Hei4 = new Person("黑人小哥4");
Person ming = new Person("小明");
ming.died += Hei1.taiGuan;
ming.died += Hei2.taiGuan;
ming.died += Hei3.taiGuan;
ming.died += Hei4.taiGuan;
ming.fellFull += Hei1.seeFull;
ming.fellFull += Hei2.seeFull;
ming.fellFull += Hei3.seeFull;
ming.fellFull += Hei4.seeFull;
ming.fellHungry += Hei1.seeHungry;
ming.fellHungry += Hei2.seeHungry;
ming.fellHungry += Hei3.seeHungry;
ming.fellHungry += Hei4.seeHungry;
int i;
try {
while (true){
Console.WriteLine("请输入小明吃甜甜圈的数量:");
i = int.Parse(Console.ReadLine());
ming.EatDonut(i);
Console.ReadKey();
Console.Clear();
}
}
catch (Exception e) {
}
}
}
class Person{
string name;
public event func died;
public event func fellFull;
public event func fellHungry;
public Person(string name) {
this.name = name;
}
public void EatDonut(int i){
if (i <= 10)
{
fellHungry(name);
}
else {
if (i <= 100)
{
fellFull(name);
}
else {
if (i > 100) {
died(name);
}
}
}
}
public void taiGuan(string name) {
Console.WriteLine(this.name+"收到"+name+"死了,开始抬棺!奏乐!!");
}
public void seeHungry(string name)
{
Console.WriteLine(this.name + "看到" + name + "还很饿");
}
public void seeFull(string name)
{
Console.WriteLine(this.name + "看到" + name + "吃饱了");
}
}
}
利用+=注册事件以后,+=后面的函数何时触发,就由事件声明的主体来控制。因为我们都想知道这件事情发生以后,我们要预期什么效果。而声明类之外的函数,我们只能选择接受"+=“或者拒绝接收”-=“,我们无法清空”=",或者调用该事件。
本文章面向于不会使用这两个特性的初学者,如果你有更好的想法,或者希望作者写一些关于编程其他特点的文章,欢迎留言。也欢迎大神指出错误,提出更好的见解。
转自:
Solita:https://zhuanlan.zhihu.com/p/413733828