《C#图解教程》读书笔记之五:委托和事件
本篇已收录至《C#图解教程》读书笔记目录贴,点击访问该目录可获取更多内容。
一、委托初窥:一个拥有方法的对象
(1)本质:持有一个或多个方法的对象;委托和典型的对象不同,执行委托实际上是执行它所“持有”的方法。如果从C++的角度来理解委托,可以将其理解为一个类型安全的、面向对象的函数指针。
(2)如何使用委托?
①声明委托类型(delegate关键字)
②使用该委托类型声明一个委托变量
③为委托类型增加方法
④调用委托执行方法
(3)委托的恒定性:
组合委托、为委托+=增加方法以及为委托-=移除方法让我们看起来像是委托被修改了,其实它们并没有被修改。事实上,委托是恒定的。
在为委托增加和移除方法时实际发生的是创建了一个新的委托,其调用列表是增加和移除后的方法结果。
(4)委托实例:
①简单带参数委托DEMO
delegate void MyDel(int value); //声明委托类型 class Program
{
void PrintLow(int value)
{
Console.WriteLine("{0} - LowValue", value);
} void PrintHigh(int value)
{
Console.WriteLine("{0} - HighValue", value);
} static void Main(string[] args)
{
Program program = new Program(); MyDel myDel; //声明委托类型 //获取0~99之间的一个随机数
Random random = new Random();
int randomValue = random.Next(); //创建一个包含具体方法的委托对象并将其赋值给myDel变量
myDel = randomValue < ?
new MyDel(program.PrintLow) : new MyDel(program.PrintHigh); //执行委托
myDel(randomValue); Console.ReadKey();
}
}
②简单无参数多方法列表委托DEMO
delegate void PrintFunction(); class Test
{
public void Print1()
{
Console.WriteLine( "Print1 -- instance" );
} public static void Print2()
{
Console.WriteLine( "Print2 -- static" );
}
} class Program
{
static void Main()
{
Test t = new Test();
PrintFunction pf;
pf = t.Print1; pf += Test.Print2;
pf += t.Print1;
pf += Test.Print2; if ( pf != null )
{
pf();
}
else
{
Console.WriteLine( "Delegate is empty" );
}
}
}
③带返回值的委托DEMO
delegate int MyDel(); class MyClass
{
int IntValue = ; public int Add2()
{
IntValue += ;
return IntValue;
} public int Add3()
{
IntValue += ;
return IntValue;
}
} class Program
{
static void Main()
{
MyClass mc = new MyClass(); MyDel mDel = mc.Add2;
mDel += mc.Add3;
mDel += mc.Add2; Console.WriteLine( "Value: {0}", mDel() );
}
}
二、匿名方法:不好意思,我匿了
在委托所持有的方法中,如果某个方法只被使用一次,这种情况下,除了创建委托语法的需要,没有必要创建独立的具名方法。因此,匿名方法应运而生。
匿名方法是在初始化委托时内联(inline)声明的方法。
下面来看看在两个版本的代码:具名方法和匿名方法的比较,匿名方法是不是简洁得多?
①具名参数
using System; class Program
{
public static int Add20( int x )
{
return x + ;
} delegate int OtherDel( int InParam );
static void Main()
{
OtherDel del = Add20; Console.WriteLine( "{0}", del( ) );
Console.WriteLine( "{0}", del( ) );
}
}
②匿名参数
using System; class Program
{
delegate int OtherDel(int InParam); static void Main()
{
OtherDel del = delegate(int x)
{
return x + ;
};
Console.WriteLine("{0}", del());
Console.WriteLine("{0}", del());
}
}
三、Lambda表达式:好吃的语法糖
(1)本质:简化语法的”语法糖“;
Lambda来源:1920年到1930年期间,数学家Alonzo Church等人发明了Lambda积分。Lambda积分是用于表示函数的一套系统,它使用希腊字母Lambda(λ)来表示无名函数。近年来,函数式编程语言(如Lisp)使用这个术语来表示可以直接描述函数定义的表达式,表达式不再需要有名字了。
(2)要点:
①Lambda表达式中的参数列表(参数数量、类型和位置)必须与委托相匹配;
②表达式中的参数列表不一定需要包含类型,除非委托有ref或out关键字(此时必须显示声明);
③如果没有参数,必须使用一组空的圆括号;
(3)语法:
四、事件初窥:发布者和订阅者模式
发布者订阅者模式定义了一种一对多的依赖关系,让多个订阅者对象同时监听某一个主题对象。这个主题对象在自身状态变化时,会通知所有订阅者对象,使它们能够自动更新自己的状态。
由订阅者提供的方法称为回调方法,因为发布者通过执行这些方法来”往回调用订阅者的方法“。还可以将它们称为事件处理程序,因为它们是为处理事件而调用的代码。
下面通过一段经典的代码来看看这个模式的应用:
using System; delegate void Handler(); class Incrementer
{
public event Handler CountedADozen; public void DoCount()
{
for ( int i=; i < ; i++ )
if ( i % == && CountedADozen != null )
CountedADozen();
}
} class Dozens
{
public int DozensCount { get; private set; } public Dozens( Incrementer incrementer )
{
DozensCount = ;
incrementer.CountedADozen += IncrementDozensCount;
} void IncrementDozensCount()
{
DozensCount++;
}
} class Program
{
static void Main()
{
Incrementer incrementer = new Incrementer();
Dozens dozensCounter = new Dozens( incrementer ); incrementer.DoCount();
Console.WriteLine( "Number of dozens = {0}",
dozensCounter.DozensCount );
}
}
五、事件全过程:声明、订阅和触发
(1)声明事件:
①事件声明在一个类中;
②附加的方法需与委托类型的签名和返回类型匹配;
③声明为public;
④无法new;
(2)订阅事件:
①使用+=为事件增加事件处理程序;
②可以使用匿名方法和Lambda表达式;
(3)触发事件:
①使用事件名称,后面跟的参数列表包含在圆括号中;
②参数列表必须与事件的委托类型相匹配;
六、走向标准之路:EventHandler
程序的异步处理是使用C#事件的绝佳场景。Windows GUI广泛地使用了事件,对于事件的使用,.NET框架提供了一个标准模式:EventHandler委托类型。
(1)第一个参数保存触发事件的对象的引用(object类型,可以匹配任何类型的实例);
(2)第二个参数保存状态信息(EventArgs类的实例),指明什么程序适用于该应用程序;
(3)返回类型为void;
现在我们来重构刚刚的订阅者类,使用标准的EventHandler委托类型:
class Dozens
{
public int DozensCount { get; private set; } public Dozens( Incrementer incrementer )
{
DozensCount = ;
incrementer.CountedADozen += IncrementDozensCount;
} void IncrementDozensCount( object source, EventArgs e )
{
DozensCount++;
}
}
那么,刚刚看到为了保持标准模式,我们只能有两个参数,第一个是触发事件的对象引用,第二个是EventArgs类的实例,如何在事件中传递数据呢?答案肯定是在第二个参数上找到切入点。我们可以声明一个派生自EventArgs的子类,在其中声明我们要传递的参数所对应的属性来保存我们需要传入的数据。TIPS:这个自定义子类的名称建议以EventArgs结尾。
public class IncrementerEventArgs : EventArgs
{
public int IterationCount { get; set; }
}
既然使用了自定义类,那么在事件的其他几部分中要使用该自定义类还必须改为泛型委托和声明自定义类对象。
class Incrementer
{
public event EventHandler<IncrementerEventArgs> CountedADozen; public void DoCount()
{
IncrementerEventArgs args = new IncrementerEventArgs();
for ( int i=; i < ; i++ )
if ( i % == && CountedADozen != null )
{
args.IterationCount = i;
CountedADozen( this, args );
}
}
}
为了在执行程序中获取到传递的数据值,便可以直接通过派生自EventArgs的自定义类的属性的到。
class Dozens
{
public int DozensCount { get; private set; } public Dozens( Incrementer incrementer )
{
DozensCount = ;
incrementer.CountedADozen += IncrementDozensCount;
} void IncrementDozensCount( object source, IncrementerEventArgs e )
{
Console.WriteLine( "Incremented at iteration: {0} in {1}",
e.IterationCount, source.ToString() );
DozensCount++;
}
}
本章思维导图
附件
思维导图(jpg、pdf以及mmap源文件)下载:http://pan.baidu.com/s/1hqA7KH2
《C#图解教程》读书笔记之五:委托和事件的更多相关文章
- C#图解教程读书笔记(第15章 委托)
委托是C#的一个很重要的知识点. 1.什么是委托 委托在我认为,就是一系列格式相同的方法列表,可能就是定义的名称不一致,参数.返回值等都是一样的. 2.如何声明委托 delegate void MyF ...
- C#图解教程读书笔记(第1章 C#和.net框架)
C#中的主要需要记住的基础概念 CLR公共语言运行库 CIL中间语言,所有的代码都会编译成中间语言. CLI公共语言基础结构 C#的优点 C#有自动垃圾回收机制
- C#图解教程读书笔记(第9章 语句)
文件头的Using是Using指令,不是using语句 using (TextWriter tw = File.CreateText("xixi.txt")) { tw.Write ...
- C#图解教程读书笔记(第8章 表达式和运算符)
表达式 字面量 整数字面量 字符字面量 字符串字面量 求值顺序 优先级 结合性 与C和C++不同,在C#中的数字不具有布尔意义. 各种运算符的作用(过) 用户定义类型转换 class XiXiInt ...
- C#图解教程读书笔记(第7章 类和继承)
1.所有的类都继承自object 2.如何隐藏基类的成员 要隐藏一个继承的数据成员,需要声明一个新的相同类型的成员,并使用相同的名称. 通过在派生类中声明新的带有相同签名的函数成员,可以隐藏或掩盖继承 ...
- C#图解教程读书笔记(第6章 类进阶)
类成员声明语句由下列部分组成:核心声明.一组可选的修饰符和一组可选的特性(attribute). [特性] [修饰符] 核心声明 修饰符: 如果有修饰符,必须放在核心声明之前. 如果有多个修饰符,要有 ...
- C#图解教程读书笔记(第5章 方法)
类型推断和var关键字 从C#3.0开始,可以在变量声明的开始部分的的位置使用新的关键字var. Var关键字并不是某种特别类型的符号.它只是句法上的速记,表示任何可以从初始化的右边推断出的类型. V ...
- C#图解教程读书笔记(第4章 类:基础)
类成员包括数据成员和函数成员. 和C/C++不同,C#在类型的外部不能声明全局变量,所有的字段都属于类型,而且必须在类型声明内部声明. 和C/C++不同,方法没有返回默认类型,所有方法必须包含返回类型 ...
- C#图解教程读书笔记(第3章 类型、存储及变量)
1.C#的中的数值不具有bool特性. 2.dynamic在使用动态语言编写的程序集时使用,这个不太明白,看到后面需要补充!! 动态化的静态类型 3.对于引用类型,引用是存放在栈中,而数据是存放在堆里 ...
- C#图解教程读书笔记(第2章 C#编程概述)
这章主要是一个对于C#程序的概括解释 和C/C++不同,不是用include声明引用的头文件,而是通过using的方式,声明引用的命名空间. 命名和C/C++类似,并且也是区分大小写的,这件事情在VB ...
随机推荐
- 总结JS 常用函数
希望本文总结的内容能给各位看官带来焕然一新的感觉.另外,如果你们有什么值得推荐的js技巧,欢迎在评论中补充,我可以收纳在本文中. PS:此文档会持续新增内容. Ajax请求 jquery ajax函数 ...
- SDN三种模型解析
数十年前,计算机科学家兼网络作家Andrew S. Tanenbaum讽刺标准过多难以选择,当然现在也是如此,比如软件定义网络模型的数量也很多.但是在考虑部署软件定义网络(SDN)或者试点之前,首先需 ...
- 第四章 使用Docker镜像和仓库(二)
第四章 使用Docker镜像和仓库(二) 回顾: 开始学习之前,我先pull下来ubuntu和fedora镜像 [#9#cloudsoar@cloudsoar-virtual-machine ~]$s ...
- Twitter Bootstrap
Twitter Bootstrap是一个HTML/CSS/JS框架,适用于移动设备优先的响应式网页开发.主要涉及: HTML:为已有的H5标签扩展了自定义属性 data-* CSS : Reset + ...
- andorid frameanimation
Android中的逐帧动画 先来说说什么是逐帧动画,逐帧动画是一种常见的动画形式(Frame By Frame),其原理是在“连续的关键帧”中分解动画动作,也就是在时间轴的每帧上逐帧绘制不同的内容,使 ...
- ZOJ3790_Consecutive Blocks
给出一个数组,最多可以删除k个数,问能够获得的最长的一个数字连续段为多少? 把所有相同的数字都提取出来,保存取得每个数字需要删除的数字,然后二分枚举就可以了. 召唤代码君: #include < ...
- php mysql支持emoji表情方案
将emoji转换成utf8 然后存储到数据库,但是 效率低 public function rrr($text) { $text = 'a
- hdoj 2034 人见人爱A-B
Problem Description 参加过上个月月赛的同学一定还记得其中的一个最简单的题目,就是{A}+{B},那个题目求的是两个集合的并集,今天我们这个A-B求的是两个集合的差,就是做集合的减法 ...
- android混淆那些事
写给Android开发者的混淆使用手册 综述 毫无疑问,混淆是打包过程中最重要的流程之一,在没有特殊原因的情况下,所有 app 都应该开启混淆. 首先,这里说的的混淆其实是包括了代码压缩.代码混淆以及 ...
- VS2013正确设置DLL环境变量目录的方法
VS有个比较迷惑人的选项,就是在工程>>属性中有个Executable Directories设置项, 几乎所有地球人都认为这个选项是设置exe可执行文件依赖的DLL目录用的,这无疑会方便 ...