[转]C# 委托、事件,lamda表达式
1. 委托Delegate
C#中的Delegate对应于C中的指针,但是又有所不同C中的指针既可以指向方法,又可以指向变量,并且可以进行类型转换,
C中的指针实际上就是内存地址变量,他是可以直接操作内存的,通过内存地址直接访问变量,直接调用方法。
而C#中的Delegate是强类型的,也就是说在声明委托时就已经指定了该变量只能指向具有特定参数,以及返回值的方法。
使用delegate就可以直接建立任何名称的委托类型,当进行系统编译时,系统就会自动生成此类型。您可以使用delegate void MyDelegate()
方式建立一个委托类,并使用ILDASM.exe观察其成员。由ILDASM.exe 中可以看到,它继承了System.MulticastDelegate类,
并自动生成BeginInvoke、EndInvoke、Invoke 等三个常用方法。
Invoke 方法是用于同步调用委托对象的对应方法,而BeginInvoke、EndInvoke是用于以异步方式调用对应方法的。
1
2
3
4
5
6
7
8
|
public class MyDelegate:MulticastDelegate { //同步调用委托方法 public virtual void Invoke(); //异步调用委托方法 public virtual IAsyncResult BeginInvoke(AsyncCallback callback, object state); public virtual void EndInvoke(IAsyncResult result); } |
MulticastDelegate是System.Delegate的子类,它是一个特殊类,编译器和其他工具可以从此类派生,但是自定义类不能显式地从此类进行派生。它支持多路广播委托,并拥有一个带有链接的委托列表,在调用多路广播委托时,系统将按照调用列表中的委托出现顺序来同步调用这些委托。
MulticastDelegate具有两个常用属性:Method、Target。其中Method 用于获取委托所表示的方法Target 用于获取当前调用的类实例。
1.1 委托的使用
当建立委托对象时,委托的参数类型必须与委托方法相对应。只要向建立委托对象的构造函数中输入方法名称example.Method,委托就会直接绑定此方法。使用myDelegate.Invoke(string message),就能显式调用委托方法。但在实际的操作中,我们无须用到 Invoke 方法,而只要直接使用myDelegate(string message),就能调用委托方法。
无返回值的委托
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
class Program { delegate void MyDelegate( string message); public class Example { public void Method( string message) { MessageBox.Show(message); } } static void Main( string [] args) { Example example= new Example(); MyDelegate myDelegate= new MyDelegate(example.Method); myDelegate( "Hello World" ); Console.ReadKey(); } } |
有返回值的委托
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
class Program { delegate string MyDelegate( string message); public class Example { public string Method( string name) { return "Hello " + name; } } static void Main( string [] args) { Example example= new Example(); //绑定委托方法 MyDelegate myDelegate= new MyDelegate(example.Method); //调用委托,获取返回值 string message = myDelegate( "Leslie" ); Console.WriteLine(message); Console.ReadKey(); } } |
多路广播委托
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
27
28
29
30
|
delegate double MyDelegate( double message); public class Price { public double Ordinary( double price) { double price1 = 0.95 * price; Console.WriteLine( "Ordinary Price : " +price1); return price1; } public double Favourable( double price) { double price1 = 0.85 * price; Console.WriteLine( "Favourable Price : " + price1); return price1; } static void Main( string [] args) { Price price = new Price(); //绑定Ordinary方法 MyDelegate myDelegate = new MyDelegate(price.Ordinary); //绑定Favourable方法 myDelegate += new MyDelegate(price.Favourable); //调用委托 Console.WriteLine( "Current Price : " + myDelegate(100)); Console.ReadKey(); } } |
输出
1.2 委托的协变与逆变
前面已经说过,委托是强类型的方法指针,但是在面对具有继承关系类型的参数、或者返回值时,委托是如何处理的呢。
协变(返回值类型具有继承关系的方法)
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
27
28
29
30
|
public class Worker {.......} public class Manager:Worker {.......} class Program { public delegate Worker GetWorkerHandler( int id); //在 Framework2.0 以上,委托 GetWorkerHandler 可绑定 GetWorker 与 GetManager 两个方法 public static Worker GetWorker( int id) { Worker worker = new Worker(); return worker; } public static Manager GetManager( int id) { Manager manager = new Manager(); return manager; } static void Main( string [] args) { GetWorkerHandler workerHandler = new GetWorkerHandler(GetWorker); Worker worker=workerHandler(1); GetWorkerHandler managerHandler = new GetWorkerHandler(GetManager); Manager manager = managerHandler(2) as Manager; Console.ReadKey(); } } |
委托 GetWorkerHandler 可以绑定 GetWorker 与 GetManager 两个方法
逆变
委托逆变,是指委托方法的参数同样可以接收 “继承” 这个传统规则。像下面的例子,以 object 为参数的委托,可以接受任何 object 子类的对象作为参数。最后可以在处理方法中使用 is 对输入数据的类型进行判断,分别处理对不同的类型的对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
class Program { public delegate void Handler( object obj); public static void GetMessage( object message) { if (message is string ) Console.WriteLine( "His name is : " + message.ToString()); if (message is int ) Console.WriteLine( "His age is : " + message.ToString()); } static void Main( string [] args) { Handler handler = new Handler(GetMessage); handler(29); Console.ReadKey(); } } |
注意:委托与其绑定方法的参数必须一至,即当 Handler 所输入的参数为 object 类型,其绑定方法 GetMessage 的参数也必须为 object 。否则,即使绑定方法的参数为 object 的子类,系统也无法辨认。
大家可能注意到了,这个委托方法GetMessage的实现不是那么优雅,于是泛型委托应运而生。
class Program { public delegate void Handler<T>(T obj); public static void GetWorkerWages(Worker worker) { Console.WriteLine("Worker's total wages is " + worker.Wages); } public static void GetManagerWages(Manager manager) { Console.WriteLine("Manager's total wages is "+manager.Wages); } static void Main(string[] args) { Handler<Worker> workerHander = new Handler<Worker>(GetWorkerWages); Worker worker = new Worker(); worker.Wages = 3000; workerHander(worker); Handler<Manager> managerHandler = new Handler<Manager>(GetManagerWages); Manager manager = new Manager(); manager.Wages = 4500; managerHandler(manager); Console.ReadKey(); } }
2. event事件的由来
事件是特殊的委托,他为委托提供了封装性,一方面允许从类的外部增加,删除绑定方法,另一方面又不允许从类的外部来触发委托所绑定了方法。
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
public delegate double PriceHandler(); public class PriceManager { public PriceHandler GetPriceHandler; //委托处理,当价格高于100元按8.8折计算,其他按原价计算 public double GetPrice() { if (GetPriceHandler.GetInvocationList().Count() > 0) { if (GetPriceHandler() > 100) return GetPriceHandler()*0.88; else return GetPriceHandler(); } return -1; } } class Program { static void Main( string [] args) { PriceManager priceManager = new PriceManager(); //调用priceManager的GetPrice方法获取价格 //直接调用委托的Invoke获取价格,两者进行比较 priceManager.GetPriceHandler = new PriceHandler(ComputerPrice); Console.WriteLine( string .Format( "GetPrice\n Computer's price is {0}!" , priceManager.GetPrice())); Console.WriteLine( string .Format( "Invoke\n Computer's price is {0}!" , priceManager.GetPriceHandler.Invoke())); Console.WriteLine(); priceManager.GetPriceHandler = new PriceHandler(BookPrice); Console.WriteLine( string .Format( "GetPrice\n Book's price is {0}!" , priceManager.GetPrice())); Console.WriteLine( string .Format( "Invoke\n Book's price is {0}!" , priceManager.GetPriceHandler.Invoke())); Console.ReadKey(); } //书本价格为98元 public static double BookPrice() { return 98.0; } //计算机价格为8800元 public static double ComputerPrice() { return 8800.0; } } |
以上代码实现了对于100元以上商品的的88折处理。一方面为了给GetPriceHandler绑定方法就必须将委托声明为public,但是一旦声明为public
就可以在类外部直接通过Invoke来调用该委托所绑定的方法,而产生我们不需要的结果。
当然我们可以将GetPriceHandler声明为private并且通过public 的addHandler,removeHandler来消除委托public的副作用,但是C#提供了更加优雅的方法:
那就是event关键字。
事件(event)可被视作为一种特别的委托,它为委托对象隐式地建立起add_XXX、remove_XXX 两个方法,用作注册与注销事件的处理方法。而且事件对应的变量成员将会被视为 private 变量,外界无法超越事件所在对象直接访问它们,这使事件具备良好的封装性,而且免除了add_XXX、remove_XXX等繁琐的代码。
1
2
3
4
5
|
public class EventTest { public delegate void MyDelegate(); public event MyDelegate MyEvent; } |
观察事件的编译过程可知,在编译的时候,系统为 MyEvent 事件自动建立add_MyEvent、remove_MyEvent 方法。
事件能通过+=和-=两个方式注册或者注销对其处理的方法,使用+=与-=操作符的时候,系统会自动调用对应的 add_XXX、remove_XXX 进行处理。
值得留意,在PersonManager类的Execute方法中,如果 MyEvent 绑定的处理方法不为空,即可使用MyEvent(string)引发事件。但如果在外界的 main 方法中直接使用 personManager.MyEvent (string) 来引发事件,系统将引发错误报告。这正是因为事件具备了良好的封装性,使外界不能超越事件所在的对象访问其变量成员。
注意:在事件所处的对象之外,事件只能出现在+=,-=的左方。
public delegate void MyDelegate(string name);
public class PersonManager
{
public event MyDelegate MyEvent;
//执行事件
public void Execute(string name)
{
if (MyEvent != null)
MyEvent(name);
}
}
class Program
{
static void Main(string[] args)
{
PersonManager personManager = new PersonManager();
//绑定事件处理方法
personManager.MyEvent += new MyDelegate(GetName);
personManager.Execute("Leslie");
Console.ReadKey();
}
public static void GetName(string name)
{
Console.WriteLine("My name is " + name);
}
}
在绑定事件处理方法的时候,事件出现在+=、-= 操作符的左边,对应的委托对象出现在+=、-= 操作符的右边。对应以上例子,事件提供了更简单的绑定方式,只需要在+=、-= 操作符的右方写上方法名称,系统就能自动辩认。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public delegate void MyDelegate( string name); public class PersonManager { public event MyDelegate MyEvent; ......... } class Program { static void Main( string [] args) { PersonManager personManager = new PersonManager(); //绑定事件处理方法 personManager.MyEvent += GetName; ............. } public static void GetName( string name) {.........} } |
如果觉得编写 GetName 方法过于麻烦,你还可以使用匿名方法绑定事件的处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public delegate void MyDelegate( string name); public class PersonManager { public event MyDelegate MyEvent; //执行事件 public void Execute( string name) { if (MyEvent != null ) MyEvent(name); } static void Main( string [] args) { PersonManager personManager = new PersonManager(); //使用匿名方法绑定事件的处理 personManager.MyEvent += delegate ( string name){ Console.WriteLine( "My name is " +name); }; personManager.Execute( "Leslie" ); Console.ReadKey(); } } |
1
|
|
3. lambda表达式
在Framework 2.0 以前,声明委托的唯一方法是通过方法命名,从Framework 2.0 起,系统开始支持匿名方法。
通过匿名方法,可以直接把一段代码绑定给事件,因此减少了实例化委托所需的编码系统开销。
而在 Framework 3.0 开始,Lambda 表达式开始逐渐取代了匿名方法,作为编写内联代码的首选方式。总体来说,Lambda 表达式的作用是为了使用更简单的方式来编写匿名方法,彻底简化委托的使用方式。
使用匿名方法
1
2
3
4
5
6
7
|
static void Main( string [] args) { Button btn = new Button(); btn.Click+= delegate ( object obj,EventArgs e){ MessageBox.Show( "Hello World !" ); }; } |
使用lambda表达式
1
2
3
4
5
6
7
|
static void Main( string [] args) { Button btn = new Button(); btn.Click+=( object obj,EventArgs e)=>{ MessageBox.Show( "Hello World !" ); }; } |
3.1常用泛型委托
public delegate bool Predicate<T>(T obj)
它是一个返回bool的泛型委托,能接受一个任意类型的对象作为参数。
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
class Program { static void Main( string [] args) { List<Person> list = GetList(); //绑定查询条件 Predicate<Person> predicate = new Predicate<Person>(Match); List<Person> result = list.FindAll(predicate); Console.WriteLine(“Person count is : ” + result.Count); Console.ReadKey(); } //模拟源数据 static List<Person> GetList() { var personList = new List<Person>(); var person1 = new Person(1, "Leslie" ,29); personList.Add(person1); ........ return personList; } //查询条件 static bool Match(Person person) { return person.Age <= 30; } } public class Person { public Person( int id, string name, int age) { ID = id; Name = name; Age = age; } public int ID { get ; set ; } public string Name { get ; set ; } public int Age { get ; set ; } } |
1
|
|
Action<T> 的使用方式与 Predicate<T> 相似,不同之处在于 Predicate<T> 返回值为 bool , Action<T> 的返回值为 void。
Action 支持0~16个参数,可以按需求任意使用。
public delegate void Action()
public delegate void Action<T1>(T1 obj1)
public delegate void Action<T1,T2> (T1 obj1, T2 obj2)
public delegate void Action<T1,T2,T3> (T1 obj1, T2 obj2,T3 obj3)
............
public delegate void Action<T1,T2,T3,......,T16> (T1 obj1, T2 obj2,T3 obj3,......,T16 obj16)
1
2
3
4
5
6
7
8
9
10
11
|
static void Main( string [] args) { Action< string > action=ShowMessage; action( "Hello World" ); Console.ReadKey(); } static void ShowMessage( string message) { MessageBox.Show(message); } |
1
|
|
委托 Func 与 Action 相似,同样支持 0~16 个参数,不同之处在于Func 必须具有返回值
public delegate TResult Func<TResult>()
public delegate TResult Func<T1,TResult>(T1 obj1)
public delegate TResult Func<T1,T2,TResult>(T1 obj1,T2 obj2)
public delegate TResult Func<T1,T2,T3,TResult>(T1 obj1,T2 obj2,T3 obj3)
............
public delegate TResult Func<T1,T2,T3,......,T16,TResult>(T1 obj1,T2 obj2,T3 obj3,......,T16 obj16)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
static void Main( string [] args) { Func< double , bool , double > func = Account; double result=func(1000, true ); Console.WriteLine( "Result is : " +result); Console.ReadKey(); } static double Account( double a, bool condition) { if (condition) return a * 1.5; else return a * 2; } |
3.2 lambda表达式
Lambda 的表达式的编写格式如下:
x=> x * 1.5
当中 “ => ” 是 Lambda 表达式的操作符,在左边用作定义一个参数列表,右边可以操作这些参数。
例子一, 先把 int x 设置 1000,通过 Action 把表达式定义为 x=x+500 ,最后通过 Invoke 激发委托。
static void Main(string[] args)
{
int x = 1000;
Action action = () => x = x + 500;
action.Invoke();
Console.WriteLine("Result is : " + x);
Console.ReadKey();
}
例子二,通过 Action<int> 把表达式定义 x=x+500, 到最后输入参数1000,得到的结果与例子一相同。
注意,此处Lambda表达式定义的操作使用 { } 括弧包括在一起,里面可以包含一系列的操作。
static void Main(string[] args)
{
Action<int> action = (x) =>
{
x = x + 500;
Console.WriteLine("Result is : " + x);
};
action.Invoke(1000);
Console.ReadKey();
}
文章来源:http://www.cnblogs.com/philzhou/archive/2012/04/05/2433663.html
[转]C# 委托、事件,lamda表达式的更多相关文章
- 委托、Lambda表达式、事件系列03,从委托到Lamda表达式
在"委托.Lambda表达式.事件系列02,什么时候该用委托"一文中,使用委托让代码简洁了不少. namespace ConsoleApplication2 { internal ...
- 委托、Lamda表达式
1.委托概述 2.匿名方法 3.语句Lambda 4.表达式Lambda 5.表达式树
- 委托、Lambda表达式、事件系列07,使用EventHandler委托
谈到事件注册,EventHandler是最常用的. EventHandler是一个委托,接收2个形参.sender是指事件的发起者,e代表事件参数. □ 使用EventHandler实现猜拳游戏 使用 ...
- 委托、Lambda表达式、事件系列06,使用Action实现观察者模式,体验委托和事件的区别
在"实现观察者模式(Observer Pattern)的2种方式"中,曾经通过接口的方式.委托与事件的方式实现过观察者模式.本篇体验使用Action实现此模式,并从中体验委托与事件 ...
- 委托、Lambda表达式、事件系列05,Action委托与闭包
来看使用Action委托的一个实例: static void Main(string[] args) { int i = 0; Action a = () => i++; a(); a(); C ...
- 委托、Lambda表达式、事件系列04,委托链是怎样形成的, 多播委托, 调用委托链方法,委托链异常处理
委托是多播委托,我们可以通过"+="把多个方法赋给委托变量,这样就形成了一个委托链.本篇的话题包括:委托链是怎样形成的,如何调用委托链方法,以及委托链异常处理. □ 调用返回类型为 ...
- 委托、Lambda表达式、事件系列02,什么时候该用委托
假设要找出整型集合中小于5的数. static void Main(string[] args) { IEnumerable<int> source = new List<int&g ...
- 委托、Lambda表达式、事件系列01,委托是什么,委托的基本用法,委托的Method和Target属性
委托是一个类. namespace ConsoleApplication1 { internal delegate void MyDelegate(int val); class Program { ...
- 委托、泛型委托、多播委托、匿名函数、lamda表达式、事件
1.为什么要使用委托 将一个方法作为参数传递给另一个方法 2.委托概念 public delegate int 委托名(int a, int b); 声明一个委托类型,可以用访问修饰符修饰,deleg ...
- [.net 面向对象程序设计进阶] (5) Lamda表达式(一) 创建委托
[.net 面向对象程序设计进阶] (5) Lamda表达式(一) 创建委托 本节导读: 通过学习Lambda表达式,学会创建委托和表达式目录树,深入了解Lambda的特性,让你的代码变的更加清晰. ...
随机推荐
- C++相对路径和绝对路径
学习备忘 转自:http://www.cnblogs.com/vranger/p/3820783.html 电脑硬盘E盘下,建文件夹“test”,"test"下建立子文件夹“fil ...
- lock tables和unlock tables
1.lock tables table1 read,table2 read,table3 read igoodful@a8-apple-iphone-db00.wh(glc) > show ta ...
- css 苹方字体
苹方-简 常规体 font-family: PingFangSC-Regular, sans-serif; 苹方-简 极细体 font-family: PingFangSC-Ultralight, s ...
- 关于Modelsim安装闪退
在盗版Windows系统上,安装Modelsim时可能出现闪退. 现象表现为,在任务管理器中仍然有Modelsim的进程,但是看不到安装界面. 碰到这种情况可以尝试如下方法:退到安全模式下安装. 一般 ...
- 【JZOJ6345】ZYB建围墙
description analysis 打表找规律,自认为样例给的提示很明显 容易想到最优方案是让家庭尽量先围成一个正六边形,剩下的在最外层绕一个圈 手推一波可以知道,如果正六边形有\(n\)层,剩 ...
- el-upload文件上传组件
一.介绍 element-ui的组件之一,用来点击上传文件 官方是使用 before-upload 限制用户上传的图片格式和大小.但是某些浏览器不支持此方法,所以使用on-change来代替. 二.代 ...
- vs数据库连接问题
在swagger上测试时报错:数据库连接不上 原因:在项目中修改过connectionstring,但是每次编译时本地文件中并没有更新 修改: 修改配置文件属性:不复制 —> 始终复制
- mfcs100d.lib(dllmodul.obj) : error LNK2005: _DllMain@12 already defined in MSVCRTD.lib(dllmain.obj)
转自VC错误:http://www.vcerror.com/?p=55 问题描述: mfcs100d.lib(dllmodul.obj) : error LNK2005: _DllMain@12 al ...
- 资源-Android:Android
ylbtech-资源-Android:Android 1.返回顶部 1. https://developer.android.google.cn/studio 2. 2.返回顶部 1. 1.1 1.2 ...
- Spring IOC基础回顾 — 组件扫描和装配
目录 注解形式配置应用IOC 1. 组件自动扫描 2. 组件依赖:为bean添加注解,实现自动注入 3. Spring IOC应用小结 注解形式配置应用IOC 在类定义.方法定义.成员变量定义前使用, ...