彻底弄懂C#中delegateeventEventHandlerActionFunc的使用和区别

Posted digital-college

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了彻底弄懂C#中delegateeventEventHandlerActionFunc的使用和区别相关的知识,希望对你有一定的参考价值。

【目录】

1 委托

2 事件-概念的引出

3 事件-关于异常

4 事件-关于异步

5 委托-Func与Action

 

1 委托

在.NET中定义“委托”需要用到delegate关键字,它是存有对某个方法的引用的一种引用类型变量,类似于 C 或 C++ 中函数的指针。“委托”主要有两大作用:

(1)将方法当作参数传递

(2)方法的一种多态(类似于一个方法模板,可以匹配很多个方法)

下面,给出一个展现了上述两大作用的委托代码示例:

        //定义一个委托
        public delegate int MyDelegate(int x, int y);

        //与委托匹配的一个方法
        public static int Add(int a, int b)
        
            return a + b;
        

        //与委托匹配的另一个方法
        public static int Reduce(int a, int b)
        
            return a - b;
        

        //示例:将委托/方法当参数传递
        public static int Test(MyDelegate MD)
        
            return MD(10, 20);
        

        static void Main(string[] args)
        
            int a, b, x, y;

            MyDelegate md;

            //将委托指向Add这个方法,并进行相关操作
            md = Add;
            a = md(1, 2);
            b = Test(md);

            //再将委托指向Reduce这个方法,并进行相关操作
            md = Reduce;
            x = md(7, 2);
            y = Test(md);

            Console.WriteLine($"1+2=a,10+20=b,7-2=x,10-20=y");
            Console.ReadLine();
        

执行以上程序,输出结果如下:

1+2=3,10+20=30,7-2=5,10-20=-10

委托也可以使用+=/-=来实现“发布/订阅”模式,示例代码如下:

        //定义一个委托
        public delegate void MyDelegate1(int x);

        public static void Method1(int a)
        
            Console.WriteLine($"a=a");
        

        public static void Method2(int b)
        
            Console.WriteLine($"b=b");
        

        static void Main(string[] args)
        
            MyDelegate1 md = null;
            md += Method1;
            md += Method2;
            md(35);

            Console.ReadLine();
        

以上程序输出如下:

a=35

b=35

但是委托有一个弊端,它可以使用“=”将所有已经订阅的取消,只保留=后的这一个订阅。

为了解决这个弊端,事件event应运而生。

 

2 事件-概念的引出

事件event是一种特殊的委托,它只能+=,-=,不能直接用=。

event在定义类中(发布者)是可以直接=的,但是在其他类中(订阅者)就只能+= -=了,也就是说发布者发布一个事件后,订阅者针对他只能进行自身的订阅和取消。

下面是定义一个事件的代码:

        //定义一个委托
        public delegate void MyDelegate1(int x);
        //定义一个事件
        public event MyDelegate1 emd;

经过长久的经验积累后,人们发现,绝大多数事件的定义,是用public delegate void XXX(object sender, EventArgs e);这样一个委托原型进行定义的,是一件重复性的工作,于是,EventHandler应运而生。它的出现就是为了避免这种重复性工作,并建议尽量使用该类型作为事件的原型。

//@sender: 引发事件的对象
//@e: 传递的参数
public delegate void EventHandler(object sender, EventArgs e);

//使用
public event EventHandler emd;


下面给出一个使用事件的具体示例:

        public class Demo
        
            public event EventHandler emd;
            public void RaiseEvent()
            
                emd(this, EventArgs.Empty);
            
        

        static void Main(string[] args)
        
            var instance = new Demo();
            instance.emd += (sender, arg) =>
            
                Console.WriteLine("执行事件1!");
            ;

            instance.emd += (sender, arg) =>
            
                Console.WriteLine("执行事件2!");
            ;

            instance.RaiseEvent();

            Console.ReadLine();
        

这里我们先定义一个Demo类,其内部有个事件是emd,我们给他开放了一个接口RaiseEvent,如果谁敢调用它,那么,它就触发报警事件emd。

这里模拟了2个订阅者,分别处理报警事件emd。

程序执行结果如下:

执行事件1!

执行事件2!

同时,我们也可以看出:事件是按照+=的订阅先后顺序执行的。

3 事件-关于异常

现在,我们在第一个订阅者中加入异常,如下:

    instance.emd += (sender, arg) =>
    
        Console.WriteLine("执行事件1!");
        throw new Exception("执行事件1,错误");
    ;

 

执行后发现,第1个订阅者事件触发抛出异常后,第2个订阅者的事件没有执行。

可见,如果你想让所有订阅者都执行处理的话,那每个订阅者必须在订阅程序内自己处理好异常,不能抛出来!

 

4 事件-关于异步

如果事件的订阅者中有一个是“异步”处理,又会是什么情况?

下面我们把第1个订阅者改为异步处理,代码如下:

    instance.emd += async (sender, arg) =>
    
        Console.WriteLine("执行事件1!");
        await Task.Delay(1000);
        Console.WriteLine("执行事件1!完毕");
    ;

执行后输出如下:
执行事件1!

执行事件2!

执行事件1!完毕

可见,异步的事件处理没有阻塞进程,很好的起到了异步方法的作用。

 

5 委托-Func与Action

本文最开始探讨委托,然后直接顺到了事件的相关话题上。其实,关于委托还有一个重点话题漏掉了,那就是Func与Action。

在委托delegate出现了很久以后,微软的.NET设计者们终于领悟到,其实所有的委托定义都可以归纳并简化成只用Func与Action这两个语法糖来表示。其中,Func代表有返回值的委托,Action代表无返回值的委托。有了它们两,我们以后就不再需要用关键字delegate来定义委托了。

同时,若再用lambda表达式取代被委托指向的具体方法,则整个委托的“定义+赋值”两步将大大简化(lambda表达式本来也是方法定义的一种简化形式)。

下面,把最开始委托章节中关于加减法的程序代码,用Func与lambda表达式进行简化改造,改造后的代码如下:

        //示例:将委托/方法当参数传递
        public static int Test(Func<int, int, int> MD)
        
            return MD(10, 20);
        

        static void Main(string[] args)
        
            int a, b, x, y;

            Func<int, int, int> md;

            //将委托指向加法,并进行相关操作
            md = (t, v) => t + v;
            a = md(1, 2);
            b = Test(md);

            //再将委托指向减法,并进行相关操作
            md = (t, v) => t - v;
            x = md(7, 2);
            y = Test(md);

            Console.WriteLine($"1+2=a,10+20=b,7-2=x,10-20=y");
            Console.ReadLine();
        

是不是代码大大简化了?简化了哪些内容,你可以前后对比一下...(本文完)

 

彻底弄懂python编码

  在编写python程序的过程中,中英文混用经常会出现编码问题。围绕此问题,本文首先介绍编码的含义及常用编码,随后列举几个python经常遇到的编码异常及解决方法,接着列举笔者在实践中遇到的异常出现的情景及原因,最后针对编码问题提出最佳实践。

一 常见编码

1.1 unicode编码

  在文本文件中,看到的所有字符,包括中文,都需要在计算机中存储,而计算机只能存储0和1这样的二进制位,所以需要一种方法,将字符映射成数字,然后将数字转化为二进制位存储在计算机中。针对字符和数字的映射的问题,产生了unicode编码,unicode将世界上的所有字符映射为唯一的数字。unicode数字并不是直接就可以转化为二进制存储,比如假设中文字符‘中’映射为数字1(00000001),‘国’映射为数字2(00000010),由于汉字很多,单字节并不能表示完所有的汉字,故可能会有汉字的unicode数字为258(00000001 00000010),假设为‘京’,现在在字符串中碰到存储为00000001 00000010的二进制串,不能区分出其实际代表的是“中国”还是“京”。

  针对unicode数字和二进制的映射问题,有两种解决方法:一种是每个unicode数字用固定宽度的二进制位表示,比如都用两字节,由此产生了ASCII、GB2312、GBK编码;另一种是存储的二进制位除了表示数字之外,还表示每个unicode数字的长度,由此产生了utf-8编码。

1.2 ASCII编码

ASCII编码用单字节表示字符,最高位固定为0,故最多只能表示128个字符,当编程只涉及到英文字符或数字时,不涉及中文字符时,可以使用ASCII编码。

1.3 GB2312编码、GBK

  GB(GuoBiao)为国标,GBK(GuoBiao Kuozhan)表示国标扩展。GB2312兼容ASCII编码,对于ASCII可以表示的字符,如英文字符‘A’、‘B’等,在GB2312中的编码和ASCII编码一致,占一个字节,对于ASCII不能表示的字符,GB2312用两个字节表示,且最高位不为0,以防和ASCII字符冲突。例如:‘A’在GB2312中存储的字节十六进制为41,在ASCII中也是41,中文字符‘中’在GB2312中存储的两个字节十六进制为D6D0,最高位为1不为0。

  GB2312只有6763个汉字,而汉字特别多。GBK属于GB2312的扩展,增加了很多汉字,同时兼容GB2312,同样用两个字节表示非ASCII字符。

1.4 UTF-8编码

  和GB系列不同,UTF-8可以将全世界所有的unicode数字表示出来。UTF-8兼容ASCII编码,不兼容GB系列编码,因此,若文本中UTF-8和GB系列编码混用,会出现乱码问题。UTF-8对于每个字符的存储,用最高二进制位开始连续1的个数表示字的长度,最高位为0表示单字节,用来兼容ASCII字符,为110表示双字节,非字符首字节的字节都以10开始,如下表格所示。例如:字符‘中’的unicode编码为2D4E(00101101 01001110),用UTF-8存储的二进制为E4B8AD(11100100 10111000 10101101 ),存储在计算机中的首字节为1110开头,表示此字符占三个字节,去掉开始字节表示长度的1110和其余字节开头的10,可以得到01001110 00101101(4E2D),可以看到和unicode数字刚好相反,是因为是大端存储方式,高字节存储在内存中的低地址端,反过来即为unicode编码。

 

字节数二进制编码格式
单字节 0XXXXXXX
双字节 110XXXXX 10XXXXXX
三字节 1110XXXX 10XXXXXX 10XXXXXX
四字节 11110XXX 10XXXXXX 10XXXXXX 10XXXXXX
五字节 111110XX 10XXXXXX 10XXXXXX 10XXXXXX 10XXXXXX
六字节 1111110X 10XXXXXX 10XXXXXX 10XXXXXX 10XXXXXX 10XXXXXX

二 python字符序列及编码问题

  上一节对几种常见的编码原理做出了介绍,以便理解python由于编码引起的异常,本节将对python中的字符串作出介绍,并在此基础上提出几种常见的编码异常,并提供解决方案。

2.1 python2和python3字符序列

   python2中字符序列有两种类型:unicode和str。unicode字符序列存储的元素为unicode字符。如图2.1所示,unicode_string代表unicode字符序列“中国”,其长度为2,恰好表示两个unicode字符。

   图2.1 unicode字符序列

  python2中的另一种字符序列是str类型,str类型的字符序列其实是unicode字符序列encode之后的值,用不同的编码类型encode,得出的值不一样。str字符序列的元素为字节,如图2.2所示,“中国” 的str字符序列长度为6,为UTF-8编码后所占字节长度。

图2.2 str字符序列

  与unicode字符串转化为str类型用encode相反,str类型的字符序列转化为unicode字符串,可以通过decode方法,如图2.3所示:

图2.3 str转化为unicode

   python3中的字符序列也有两种类型:bytes和str。python3中的bytes和python2中的str相似,str和python2中的unicode相似。这里要注意,str类型在python3和python2中都有,但含义完全变了。

图2.4 python3的str和bytes字符序列

2.2常见编码问题

2.2.1 UnicodeEncoderError

  将文本转化为字节序列时,若有字符在目标编码中没有定义,则会出现UnicodeEncoderError。如图2.5所示,由于中文字符在ascii编码中无定义,则会报出编码错误。对于此类问题,需选择合适的编码类型,比如含有中文字符,一般用UTF-8编码类型对unicode字符串编码。

图2.5 UnicodeEncodeError示例

 

2.2.2 UnicodeDecodeError

  把二进制序列转化为文本时,遇到无法转换的字节序列,则会发生此异常。比如用UTF-8编码后的二进制序列,用GB2312解码,由于两种编码不兼容,用GB2312不能识别字节序列,则会出现异常,如图2.6所示。

图2.6 UnicodeDecodeError示例

  碰到这种异常,是由于decode使用的编码和字节序列的编码不一致,可以用字符编码侦测包chardet检测字节序列的编码,然后再用此编码解码。如图2.7所示:

 

图2.7 编码检测

三 实践中常见编码异常场景

3.1 字符串连接

python代码

1 # -*- coding: utf-8 -*-
2 unicode_string=u\'中国\'
3 str_string=\'中国\'
4 merge_string= str_string+unicode_string #UnicodeDecodeError: \'ascii\' codec can\'t decode byte 0xe4 in position 0: ordinal not in range(128)

python代码

复制代码
1 # -*- coding: utf-8 -*-
2 unicode_string=u\'中国\'
3 str_string=\'中国\'
4 "中国:%s" % str_string
5 #两种字符序列混用,相当于"中国:%s".decode(\'ascii\')%unicode_string
6 "中国:%s" % unicode_string #UnicodeDecodeError: \'ascii\' codec can\'t decode byte 0xe4 in position 0: ordinal not in range(128)
7 u"中国:%s"%unicode_string
8 #两种字符序列混用,相当于u"中国:%s"%str_string.decode(\'ascii\')
9 u"中国:%s"%str_string #UnicodeDecodeError: \'ascii\' codec can\'t decode byte 0xe4 in position 0: ordinal not in range(128)
复制代码

  当str类型字符串和unicode类型字符串混合运算时,python默认会将str类型字符串转化为unicode字符串,由于不知道str类型字符串的编码格式,会使用 sys.getdefaultencoding() ,而默认的defaultencoding一般是ascii,故会出错。

3.2 print中文问题

 如图3.1,python打印变量时,操作系统会对变量进行相应的处理,若变量是str类型,则操作系统直接发送到终端显示,若变量是unicode类型,则操作系统会对变量用sys.stdout.encoding编码对变量encode,若变量中含有sys.stdout.encoding未定义字符,则会出现UnicodeEncodeError。编码后字节序列被发送给终端,假若终端设置的编码和str编码不一致,终端就会显示出乱码。

图3.1 print过程

四 最佳实践

  编写python程序时,为避免不同类型字符串混用出现编解码异常,要把编码和解码操作放在程序的最外围来做,程序的核心逻辑统一使用unicode字符类型。下面分别对python2和python3编写了外围编码转换工具类。

  

复制代码
 1 #python2,unicode和utf-8类型的str互相转换
 2 #file:python2_endecode_helper.py
 3 
 4 # -*- coding: utf-8 -*-
 5 def to_unicode(unicode_or_str):
 6     if isinstance(unicode_or_str, str):
 7         value = unicode_or_str.decode(\'UTF-8\')
 8     else:
 9         value = unicode_or_str
10     return value
11 
12 def to_str(unicode_or_str):
13     if isinstance(unicode_or_str, unicode):
14         value = unicode_or_str.encode(\'UTF-8\')
15     else:
16         value = unicode_or_str
17     return value
18 
19 if __name__==\'__main__\':
20     unicode_string = u\'中国\'
21     value = to_str(unicode_string)
22     print type(value) #<type \'str\'>
23     value = to_unicode(value)
24     print type(value) #<type \'unicode\'>
复制代码

 

复制代码
#python3,str和bytes类型相互转换工具类
#file:python3_endecode_helper.py
def to_str(bytes_or_str):
    if isinstance(bytes_or_str,bytes):
        value = bytes_or_str.decode(\'UTF-8\')
    else:
        value = bytes_or_str
    return value

def to_bytes(bytes_or_str):
    if isinstance(bytes_or_str,str):
        value = bytes_or_str.encode(\'UTF-8\')
    else:
        value = bytes_or_str
    return value

if __name__==\'__main__\':
    str_string = u\'中国\'
    value = to_bytes(str_string)
    print(type(value)) #<class \'bytes\'>
    value = to_str(value)
    print(type(value)) #<class \'str\'>
复制代码

 

参考文献 

[1] Brett Slatkin. Effective Python[M]. 北京: 机械工业出版社, 2016: 5-7
[2] Luciano Ramalho. Fluent Python[M]. 北京: 人民邮电出版社, 2017: 89- 91
[3] Jinhaolin. python2编码总结. https://www.cnblogs.com/jinhaolin/p/5128973.html
[4] In355hz. 也谈 Python 的中文编码处理. http://in355hz.iteye.com/blog/1860787
[5] 董公子. python中文编码问题:print打印中文异常及显示乱码问题分析与解决. https://blog.csdn.net/qq_26580757/article/details/79922043

 

本文为转载,原文链接:https://www.cnblogs.com/killianxu/p/9746545.html

以上是关于彻底弄懂C#中delegateeventEventHandlerActionFunc的使用和区别的主要内容,如果未能解决你的问题,请参考以下文章

彻底弄懂python编码

彻底弄懂python编码

一文让你彻底弄懂MySQL自增列

实战篇是时候彻底弄懂BERT模型了(建议收藏)

7000字+24张图,带你彻底弄懂线程池

这一次,彻底弄懂 Promise 原理