C# 从1到Core–委托与事件

委托与事件在c#1.0的时候就有了,随着c#版本的不断更新,有些写法和功能也在不断改变。本文温故一下这些改变,以及在net core中关于事件的一点改变。

一、c#1.0 从委托开始

1. 基本方式

什么是委托,就不说概念了,用例子说话。

某hr说他需要招聘一个6年 .net5 研发经验的“高级”工程师,他想找人(委托)别人把这条招聘消息发出去。这样的hr很多,所以大家定义了一个通用的发消息规则:

public delegate string senddelegate(string message);

这就像一个接口的方法,没有实际的实现代码,只是定义了这个方法有一个string的参数和返回值。所有想发招聘消息的hr只要遵守这样的规则即可。

委托本质上是一个类,所以它可以被定义在其他类的内部或外部,根据实际引用关系考虑即可。本例单独定义在外部。

为hr定义了一个名为hr的类:

public class hr
{
    public senddelegate senddelegate;
    public void sendmessage(string msg)
    {
        senddelegate(msg);
    }
}

hr有一个senddelegate类型的成员,当它需要发送消息(sendmessage)的时候,只需要调用这个senddelegate方法即可。而不需要实现这个方法,也不需要关心这个方法是怎么实现的。

当知道这个hr需要发送消息的时候,猎头张三接了这个帮忙招人的工作。猎头的类为sender,他有一个用于发送消息的方法send,该方法恰好符合众人定义的名为senddelegate的发消息规则。这有点像实现了一个接口方法,但这里不要求方法名一致,只是要求方法的签名一致。

public class sender
{
    public sender(string name)
    {
        this.sendername = name;
    }

    private readonly string sendername;
    public string send(string message)
    {
        string serialnumber = guid.newguid().tostring();
        console.writeline(sendername + " sending....");
        thread.sleep(2000);
        console.writeline("sender: " + sendername + " , content: " + message + ", serial number: "  + serialnumber);
        return serialnumber;
    }
}

猎头帮助hr招人的逻辑如下:

public void test()
{
    //一个hr
    hr hr = new hr();

    //猎头张三来监听,听到hr发什么消息后立刻传播出去
    sender senderzs = new sender("张三");
    hr.senddelegate = senderzs.send;

    //hr递交消息
    hr.sendmessage("hello world");
}

猎头将自己的发消息方法“赋值”给了hr的senddelegate方法,为什么可以“赋值”? 因为二者都遵守senddelegate规则。 就像a和b两个变量都是int类型的时候,a可以赋值给b一样。

这就是一个简单的委托过程,hr将招人的工作委托给了猎头,自己不用去做招人的工作。

但经常一个招聘工作经常会有多个猎头接单,那就有了多播委托。

2. 多播委托

 看一下下面的代码:

public void test()
{
    //一个hr
    hr hr = new hr();

    //猎头张三来监听,听到hr发什么消息后立刻传播出去
    sender senderzs = new sender("张三");
    hr.senddelegate = senderzs.send;

    //快嘴李四也来了
    sender senderls = new sender("李四");
    hr.senddelegate += senderls.send;
    //hr递交消息
    hr.sendmessage("hello world");
}

与之前的代码改变不大, 只是添加了李四的方法绑定,这样hr发消息的时候,张三和李四都会发出招人的消息。

这里要注意李四绑定方法的时候,用的是+=而不是=,就像拼接字符串一样,是拼接而不是赋值,否则会覆盖掉之前张三的方法绑定。

对于第一个绑定的张三,可以用=号也可以用+=(记得之前好像第一个必须用=,实验了一下现在二者皆可)。

这同时也暴露了一些问题:

  • 如果后面的猎头接单的时候不小心(故意)用了=号, 那么最终前面的人的绑定都没有了,那么他将独占这个hr客户,hr发出的消息只有他能收到。
  • 可以偷偷的调用猎头的hr.senddelegate
public void test()
{
    //一个hr
    hr hr = new hr();

    //大嘴张三来监听,听到hr发什么消息后立刻传播出去
    sender senderzs = new sender("张三");
    //hr.senddelegate -= senderzs.send; //即使未进行过+=  直接调用-=,也不会报错
    hr.senddelegate += senderzs.send;

    //快嘴李四也来了
    sender senderls = new sender("李四");
    hr.senddelegate += senderls.send;

    //移除
    //hr.senddelegate -= senderzs.send;

    //风险:注意上面用的符号是+=和-=   如果使用=,则是赋值操作,
    //例如下面的语句会覆盖掉之前所有的绑定
    //hr.senddelegate = senderww.send;

    //hr递交消息
    hr.sendmessage("hello world");

    //风险:可以偷偷的以hr的名义偷偷的发了一条消息    senddelegate应该只能由hr调用   
    hr.senddelegate("偷偷的发一条");

}

3. 通过方法避免风险

很自然想到采用类似get和set的方式避免上面的问题。既然委托可以像变量一样赋值,那么也可以通过参数来传值,将一个方法作为参数传递。

 

    public class hrwithaddremove
    {
        private senddelegate senddelegate;

        public void adddelegate(senddelegate senddelegate)
        {
            this.senddelegate += senddelegate; //如果需要限制最多绑定一个,此处可以用=号
        }

        public void romovedelegate(senddelegate senddelegate)
        {
            this.senddelegate -= senddelegate;
        }

        public void sendmessage(string msg)
        {
            senddelegate(msg);
        }
    }

经过改造后的hr,senddelegate方法被设置为了private,之后只能通过add和remove的方法进行方法绑定。

4.模拟多播委托机制

通过上面委托的表现来看,委托就像是保存了一个相同方法名的集合 list<senddelegate> ,可以向集合中添加或移除方法,当调用这个委托的时候,会逐一调用该集合中的各个方法。

例如下面的代码( 注意这里假设senddelegate只对应一个方法 ):

public class hr1
{
    public void delegate(senddelegate senddelegate)
    {
        senddelegatelist = new list<senddelegate> { senddelegate }; //对应=
    }

    public void adddelegate(senddelegate senddelegate)
    {
        senddelegatelist.add(senddelegate); //对应+=
    }

    public void romovedelegate(senddelegate senddelegate)
    {
        senddelegatelist.remove(senddelegate);//对应-=
    }

    public list<senddelegate> senddelegatelist;

    public void sendmessage(string msg)
    {
        foreach (var item in senddelegatelist)
        {
            item(msg);
        }
    }
}

二、c#1.0 引入事件

  1.简单事件

如果既想使用-=和+=的方便,又想避免相关功能开闭的风险怎么办呢?可以使用事件:

    public class hrwithevent
    {
        public event senddelegate senddelegate;
        public void sendmessage(string msg)
        {
            senddelegate(msg);
        }
    }

只是将senddelegate前面添加了一个event标识,虽然它被设置为public,但如下代码却会给出错误提示: 事件“hrwithevent.senddelegate”只能出现在 += 或 -= 的左边(从类型“hrwithevent”中使用时除外) 

 hr.senddelegate = senderzs.send;
 hr.senddelegate("偷偷的发一条");

  2.事件的访问器模式

   上文为委托定义了add和remove方法,而事件支持这样的访问器模式,例如如下代码:

    public class customerwitheventaddremove
    {
        private event senddelegate senddelegate;

        public event senddelegate senddelegate
        {
            add { senddelegate += value; }
            remove { senddelegate -= value; }
        }
        public void sendmessage(string msg)
        {
            senddelegate(msg);
        }
    }

 

可以像使用get和set方法一样,对事件的绑定与移除进行条件约束。 

  3. 控制绑定事件的执行

当多个委托被绑定到事件之后,如果想精确控制各个委托的运行怎么办,比如返回值(虽然经常为void)、异常处理等。

第一章第4节通过一个list<senddelegate> 模拟了多播委托的绑定。 会想到如果真能循环调用一个个已绑定的委托,就可以精确的进行控制了。那么这里说一下这样的方法:

 

    public class hrwithevent
    {
        public event senddelegate senddelegate;
        public void sendmessage(string msg)
        {
            //senddelegate(msg);  此处不再一次性调用所有
            if (senddelegate != null)
            {
                delegate[] delegates = senddelegate.getinvocationlist(); //获取所有已绑定的委托
                foreach (var item in delegates)
                {
                    ((senddelegate)item).invoke(msg); //逐一调用
                }
            }

        }
    }

这里通过invoke方法逐一调用各个delegate,从而实现对每一个delegate的调用的控制。若需要异步调用,则可以通过begininvoke方法实现(.net core之后不再支持此方法,后面会介绍。)

((senddelegate)item).begininvoke(msg,null,null);

  4. 标准的事件写法

.net 事件委托的标准签名是:

void oneventraised(object sender, eventargs args);

 

  返回类型为 void。 事件基于委托,而且是多播委托。 参数列表包含两种参数:发件人和事件参数。 sender 的编译时类型为 system.object

  第二种参数通常是派生自 system.eventargs 的类型.net core 中已不强制要求继承自system.eventargs,后面会说到)

  将上面的例子修改一下,改成标准写法,大概是下面代码的样子:

public class hrwitheventstandard
{
    public delegate void sendeventhandler(object sender, sendmsgargs e);
    public event sendeventhandler send;
    public void sendmessage(string msg)
    {
        var arg = new sendmsgargs(msg);
        send(this,arg); //arg.cancelrequested 为最后一个的值   因为覆盖
    }
}

public class sendmsgargs : eventargs
{
    public readonly string msg = string.empty;
    public bool cancelrequested { get; set; }
    public sendmsgargs(string msg)
    {
        this.msg = msg;
    }
}

 

 

三、随着c#版本改变

1. c#2.0 泛型委托

c#2.0 的时候,随着泛型出现,支持了泛型委托,例如,在委托的签名中可以使用泛型,例如下面代码

public delegate string senddelegate<t>(t message);

这样的委托适用于不同的参数类型,例如如下代码(注意使用的时候要对应具体的类型)

public delegate string senddelegate<t>(t message);

public class hr1
{
    public senddelegate<string> senddelegate1;
    public senddelegate<int> senddelegate2;
    public senddelegate<datetime> senddelegate3;
}

public static class sender1
{
    public static string send1(string msg)
    {
        return "";
    }

    public static string send2(int msg)
    {
        return "";
    }
}
    
public class test
{
    public void testdemo()
    {
        hr1 hr1 = new hr1();
        hr1.senddelegate1 = sender1.send1; // 注意使用的时候要对应具体的类型
        hr1.senddelegate2 = new senddelegate<int>(sender1.send2);
        hr1.senddelegate3 = delegate (datetime datetime) { return datetime.tolongdatestring(); };

    }
}

2. c#2.0 delegate运算符

delegate 运算符创建一个可以转换为委托类型的匿名方法:

例如上例中这样的代码:

hr1.senddelegate3 = delegate (datetime datetime) { return datetime.tolongdatestring(); };

3. c#3.0 lambda 表达式

从 c# 3 开始,lambda 表达式提供了一种更简洁和富有表现力的方式来创建匿名函数。 使用 => 运算符构造 lambda 表达式,

例如“delegate运算符”的例子可以简化为如下代码:

hr1.senddelegate3 = (datetime) => { return datetime.tolongdatestring(); };

 

4.c#3,net framework3.5,action 、func、predicate

action 、func、predicate本质上是框架为我们预定义的委托,在上面的例子中,我们使用委托的时候,首先要定义一个委托类型,然后在实际使用的地方使用,而使用委托只要求方法名相同,在泛型委托出现之后,“定义委托”这一操作就显得越来越累赘,为此,系统为我们预定义了一系列的委托,我们只要使用即可。

例如action的代码如下:

    public delegate void action();
    public delegate void action<in t>(t obj);
    public delegate void action<in t1, in t2>(t1 arg1, t2 arg2);
    public delegate void action<in t1, in t2, in t3>(t1 arg1, t2 arg2, t3 arg3);
    public delegate void action<in t1, in t2, in t3, in t4>(t1 arg1, t2 arg2, t3 arg3, t4 arg4);
    public delegate void action<in t1, in t2, in t3, in t4, in t5>(t1 arg1, t2 arg2, t3 arg3, t4 arg4, t5 arg5);
    public delegate void action<in t1, in t2, in t3, in t4, in t5, in t6>(t1 arg1, t2 arg2, t3 arg3, t4 arg4, t5 arg5, t6 arg6);
    public delegate void action<in t1, in t2, in t3, in t4, in t5, in t6, in t7>(t1 arg1, t2 arg2, t3 arg3, t4 arg4, t5 arg5, t6 arg6, t7 arg7);
    public delegate void action<in t1, in t2, in t3, in t4, in t5, in t6, in t7, in t8>(t1 arg1, t2 arg2, t3 arg3, t4 arg4, t5 arg5, t6 arg6, t7 arg7, t8 arg8);
    public delegate void action<in t1, in t2, in t3, in t4, in t5, in t6, in t7, in t8, in t9>(t1 arg1, t2 arg2, t3 arg3, t4 arg4, t5 arg5, t6 arg6, t7 arg7, t8 arg8, t9 arg9);
    public delegate void action<in t1, in t2, in t3, in t4, in t5, in t6, in t7, in t8, in t9, in t10>(t1 arg1, t2 arg2, t3 arg3, t4 arg4, t5 arg5, t6 arg6, t7 arg7, t8 arg8, t9 arg9, t10 arg10);
    public delegate void action<in t1, in t2, in t3, in t4, in t5, in t6, in t7, in t8, in t9, in t10, in t11>(t1 arg1, t2 arg2, t3 arg3, t4 arg4, t5 arg5, t6 arg6, t7 arg7, t8 arg8, t9 arg9, t10 arg10, t11 arg11);
    public delegate void action<in t1, in t2, in t3, in t4, in t5, in t6, in t7, in t8, in t9, in t10, in t11, in t12>(t1 arg1, t2 arg2, t3 arg3, t4 arg4, t5 arg5, t6 arg6, t7 arg7, t8 arg8, t9 arg9, t10 arg10, t11 arg11, t12 arg12);
    public delegate void action<in t1, in t2, in t3, in t4, in t5, in t6, in t7, in t8, in t9, in t10, in t11, in t12, in t13>(t1 arg1, t2 arg2, t3 arg3, t4 arg4, t5 arg5, t6 arg6, t7 arg7, t8 arg8, t9 arg9, t10 arg10, t11 arg11, t12 arg12, t13 arg13);
    public delegate void action<in t1, in t2, in t3, in t4, in t5, in t6, in t7, in t8, in t9, in t10, in t11, in t12, in t13, in t14>(t1 arg1, t2 arg2, t3 arg3, t4 arg4, t5 arg5, t6 arg6, t7 arg7, t8 arg8, t9 arg9, t10 arg10, t11 arg11, t12 arg12, t13 arg13, t14 arg14);
    public delegate void action<in t1, in t2, in t3, in t4, in t5, in t6, in t7, in t8, in t9, in t10, in t11, in t12, in t13, in t14, in t15>(t1 arg1, t2 arg2, t3 arg3, t4 arg4, t5 arg5, t6 arg6, t7 arg7, t8 arg8, t9 arg9, t10 arg10, t11 arg11, t12 arg12, t13 arg13, t14 arg14, t15 arg15);
    public delegate void action<in t1, in t2, in t3, in t4, in t5, in t6, in t7, in t8, in t9, in t10, in t11, in t12, in t13, in t14, in t15, in t16>(t1 arg1, t2 arg2, t3 arg3, t4 arg4, t5 arg5, t6 arg6, t7 arg7, t8 arg8, t9 arg9, t10 arg10, t11 arg11, t12 arg12, t13 arg13, t14 arg14, t15 arg15, t16 arg16);

实际上定义了最多16个参数的无返回值的委托。

func与此类似,是最多16个参数的有返回值的委托。predicate则是固定一个参数以及bool类型返回值的委托。

public delegate bool predicate<t>(t obj);

 5. .net core 异步调用

第2.3节中,提示如下代码在.net core中已不支持

((senddelegate)item).begininvoke(msg,null,null);

 

会抛出异常:

system.platformnotsupportedexception:“operation is not supported on this platform.”

 

需要异步调用的时候可以采用如下写法:

task task = task.run(() => ((senddelegate)item).invoke(msg));

 

对应的 endinvoke() 则改为: task.wait(); 

 

 5. .net core的 eventhandler<teventargs>

.net core 版本中,eventhandler<teventargs> 定义不再要求 teventargs 必须是派生自 system.eventargs 的类, 使我们使用起来更为灵活。

例如我们可以有这样的写法:

eventhandler<string> sendnew

 

这在以前的版本中是不允许的。

 

本文链接:https://2i3i.com/12879190.html ,转载请注明来源地址。
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇