(28)C#委托,匿名函数,lambda表达式,事件
一、委托
委托是一种用于封装命名和匿名方法的引用类型。
把方法当参数,传给另一个方法(这么说好理解,但实际上方法不能当参数,传入的是委托类型),委托是一种引用类型,委托里包含很多方法的引用
创建的方法和声明的委托返回值类型相同,参数个数相同,参数类型相同时。 这个方法就满足属于这个委托(创建的方法是 public 或者是static 都没影响,只要前三项满足即可)
用普通方式创建一个委托
//声明委托(声明委托的位置也可以在class内部) public delegate string del(double a, double b); class Program { //定义一个求和方法 public string sumResult(double a, double b) { double sum = a + b; string str1 = Convert.ToString(sum); string str2 = "结果等于" + str1; return str2; } //定义一个乘积方法 public string multiplyResult(double a, double b) { double sum = a * b; string str1 = Convert.ToString(sum); string str2 = "结果等于" + str1; return str2; } static void Main(string[] args) { Program p = new Program(); //加载一个相加方法 del sr = new del(p.sumResult);
//可以简便方式加载一个方法 del mr = p.multiplyResult; //调用委托对象传入参数,用string类型接收结果 string str1 = sr(2, 3); string str2 = mr(2, 3); Console.WriteLine(str1); Console.WriteLine(str2); Console.ReadKey(); } }
引用静态方法的方式
//声明委托 public delegate string del(double a, double b); //定义一个静态方法 public static string sumResult(double a, double b) { double sum = a + b; string str1 = Convert.ToString(sum); string str2 = "结果等于" + str1; return str2; } static void Main(string[] args) { //用普通方式创建一个委托对象 del sr = sumResult; //用string类型接收结果 string str = sr(2.5, 3.5); Console.WriteLine(str); Console.ReadKey(); }
把多个方法存到一个数组,利用循环来调用不同方法
//定义个委托,传入一个double参数,返回一个double。 delegate int delInt(int a); class Program { static void Main(string[] args) { //定义并初始化一个数组。数组的类型为委托类型 --没想到还以用委托当类型 delInt[] delArray = { sumMethod.addTwo, sumMethod.XTwo}; for (int i = 0; i < delArray.Length; i++) { Console.WriteLine("循环{0}",i); display(delArray[i], 5); display(delArray[i], 10); } Console.ReadKey(); } //显示的方法 static void display(delInt del,int a) { int result = del(a); Console.WriteLine("传入{0},计算结果为{1}", a, result); } } //计算类 class sumMethod { //加2 public static int addTwo(int a) { return a + 2; } //乘2 public static int XTwo(int a) { return a * 2; } }
结果
到此为止以上的例子不能说明委托的意义,这些例子都能在不适用委托的情况下编写。
代表委托用法的例子
...
...
...
二、委托的多播
包含多个方法的委托叫做多播。
委托对象可使用 "+" 运算符进行合并。一个合并委托调用它所合并的两个委托。只有相同类型的委托可被合并。"-" 运算符可用于从合并的委托中移除组件委托。
调用多播可以按顺序连续调用多个方法,委托的返回类型必须是void,否则只能得到委托调用最后一个方法的结果。
delegate void del(int sum);//声明一个委托
class MyClass
{
public void shuchuB(int x)
{
Console.WriteLine(10 * x);
Console.ReadLine();
}
}
class Program
{
static void shuchuA(int x)
{
Console.WriteLine(20 * x);
Console.ReadLine();
}
static void Main(string[] args)
{
MyClass mc = new MyClass();
del a = shuchuA; //创建一个委托实例,持有一个静态方法
del b = mc.shuchuB; //持有一个实例方法
del c = a + b; //持有以上两个方法
c(5);
}
}
如果
del c = b+a+b;
则
还有自加自的方式,如果一个del里有两个相同的方法,他会优先删除后进的。(后进先出)
del b += a;
del b -= a;
多播委托可能遇到的问题
如果委托调用的其中一个方法抛出异常,整个迭代就会停止
static void Main(string[] args) { //当一个委托没有参数也没有返回值时,就可以直接使用Action委托,无需声明 Action action = one; action += two; try { action(); } catch (Exception) { Console.WriteLine("exception"); } Console.ReadKey(); } static void one() { Console.WriteLine("one"); throw new Exception("Error"); } static void two() { Console.WriteLine("two"); }
为避免此问题,需要迭代方法列表
static void Main(string[] args) { //当一个委托没有参数也没有返回值时,就可以直接使用Action委托,无需声明 Action action = one; action += two; //委托的GetInvocationList方法会按照加入顺序返回一个委托数组 Delegate[] dels = action.GetInvocationList(); foreach (Action act in dels) { try { act(); } catch (Exception) { Console.WriteLine("exception"); } } Console.ReadKey(); }
三、泛型委托
1
2.
Action 简化了delegate的写法
Action返回类型必须是void,可以1-16任意参数类型<参数类型1,参数类型2,..>
也可以传不同参数类型
用var快速定义
3.有返回类型用Func
最后一个参数类型为返回类型
三、匿名函数
匿名方法有两种:匿名方法、Lambda表达式
在 2.0 之前的 C# 版本中,声明委托的唯一方法是使用命名方法。
C# 2.0 引入了匿名方法,而在 C# 3.0 及更高版本中,Lambda 表达式取代了匿名方法,作为编写内联代码的首选方式.
1.匿名方法
匿名方法比普通的委托写起来简练易读,在只一次调用方法时使用起来比较方便
语法:在创建委托实例的同时紧接着用花括号写入方法
public delegate string del(参数1,参数2,...); A a =new A(参数1,参数2,...) { ..... ..... return string类型; };
注意几点:
(1)A 是一个委托
(2)创建委托实例时参数的个数和类型都必须与声明委托时一致
(3)花括号里的返回类型必须要与声明的返回类型一致
(4)末尾别忘加 ;
用匿名方式创建一个委托对象
class Program { //也可以外部声明 public delegate string del(double a, double b); static void Main(string[] args) { Program p =new Program(); //用匿名方式创建一个委托对象 del sr = delegate(double a, double b) { double sum = a + b; string str1 = Convert.ToString(sum); string str2 = "结果等于" + str1; return str2; }; //用string类型接收结果 string str = sr(2.5, 3.5); Console.WriteLine(str); Console.ReadKey(); } }
2、用lambda方法实现
通过使用 lambda 表达式,可以写入可作为参数传递或作为函数调用值返回的本地函数。 Lambda 表达式对于编写 LINQ 查询表达式特别有用。
调用时声明的方法,用一次就没。不用再单独声明个方法
格式:
若要创建 lambda 表达式,需要在 Lambda 运算符 =>左侧指定输入参数(如果有),然后在另一侧输入表达式或语句块。
例如,lambda 表达式 x => x * x
指定名为 x
的参数并返回 x
的平方值
单行lambda 表达式
class Program { public delegate double del(double a, double b); static void Main(string[] args) { Program p = new Program(); //用lambda表达式的方法 del sr = (a, b) => a + b; //传入多个参数要加括号,如果是一个参数可以写成 //del sr = a => a * a; //开头注意声明的类型和下面这个接收的变量都是double double d = sr(2.5, 3.5); Console.WriteLine(d); Console.ReadKey(); } }
多行lambda 表达式
单行表达式省略了return、花括号和分号。用多行表达式时都要补充上。
Program p = new Program(); del sr = (a, b) => { return a + b; };
泛型Lambda,可以重用
语法糖可省略 lambda参数类型
语法糖 不需要再创建实例,省略了 new Func<int,int,int>
这个是最常用的方式
进阶
泛型委托类型推断,省略了 <int>
3.闭包
。。。。。
委托隐式异步调用
使用接口取代委托减少复杂性和安全性(java没有委托,就是用接口的方式)
四、事件
事件是一种类似于订阅/发布的机制。
把订阅当成一个方法,每次一订阅就会存一个方法进入委托中。等到发布的时候,一次性的发消息给哪些订阅。
例如:
日本丰田公司2017年生产了三款新车,(汉兰达2018,凯美瑞2018,雷凌2018)。
家住天津王兰庄附近的‘大刘’和‘小明’从网上看到消息后给王兰庄广汽丰田4S点打电话了解这三款车
4S点销售员告诉他们新车还没到货,让他们留下联系方式,等车到店了给你们发短信通知他们来店里看实车。
创建一个控制台程序
再新建一个事件发布类:ReleaseEventArgs
再新建一个事件监听类:Consumer
class ReleaseEventArgs : EventArgs { public ReleaseEventArgs(string car) { this.Car = car; } public string Car { get; set; } public class Shop { // public event EventHandler<ReleaseEventArgs> NewCarClass; public void NewCar(string car) { Console.WriteLine("{0}",car); raise(car); } protected virtual void raise(string car) { EventHandler<ReleaseEventArgs> newCarClass = NewCarClass; if (newCarClass != null) { newCarClass(this, new ReleaseEventArgs(car)); } } } }
class Consumer { private string name; public Consumer(string name) { this.name = name; } public void CarArriving(object sender, ReleaseEventArgs e) { Console.WriteLine("尊敬的{0},王兰庄4S点提示您:{1}到货了",name,e.Car); } }
class Program { static void Main(string[] args) { var shop = new ReleaseEventArgs.Shop(); var daliu = new Consumer("大刘"); var xiaoming = new Consumer("小明"); shop.NewCarClass += daliu.CarArriving; shop.NewCarClass += xiaoming.CarArriving; shop.NewCar("汉兰达2018"); shop.NewCarClass -= daliu.CarArriving; shop.NewCar("凯美瑞2018"); var zhoujie = new Consumer("周杰"); shop.NewCarClass += zhoujie.CarArriving; shop.NewCar("雷凌2018"); Console.ReadKey(); } }
https://www.cnblogs.com/gq0324/p/8177799.html
事件
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using System; namespace ConsoleApp4 { class Program { static void Main(string[] args) { Business bus = new Business(); bus.Price = 11; bus.Changed += Test_Changed; bus.Price = 22; bus.Price = 33; Console.WriteLine("Hello World!"); void Test_Changed(object sender, ChangeEventArgs e) { if ((e.newPrice - e.oldPrice) > 10) { Console.WriteLine("大于10"); } } } } //标准事件模式 //一个继承事件类的子类 public class ChangeEventArgs : EventArgs { public int oldPrice; public int newPrice; //ctor + tab public ChangeEventArgs(int oldPrice, int newPrice) { this.oldPrice = oldPrice; this.newPrice = newPrice; } } public class Business { int price; public event EventHandler<ChangeEventArgs> Changed; protected virtual void OnChange(ChangeEventArgs e) { Changed?.Invoke(this, e); } public int Price { get { return price; } set { if (price == value) return; int oldPrice = price; price = value; OnChange(new ChangeEventArgs(oldPrice, price)); } } } }
------------------------------------------------
为什么要用委托?
//定义 public delegate void del(int value);
实际上相当于:定义了一个类
public class del:System.MulticastDelegate { //构造函数 public del(Object @object, IntPtr method); // public virtual void Invoke(Int32 value); //回调方法的异步回调 public virtual IAsyncResult BeginInvoke(Int32 value, AsyncCallback callback, Object @object); //回调方法的异步回调 public virtual void EndInvoke(IAsyncResult result); }
异步调用完成时执行回调方法
异步启动委托, 参数与要执行的方法的参数相同,另加两个可选参数
第一个参数是一个 AsyncCallback 委托,此委托引用在异步调用完成时要调用的方法。
第二个参数是一个用户定义的对象,该对象将信息传递到回调方法。
BeginInvoke
将立即返回,而不会等待异步调用完成。
BeginInvoke
返回可用于监视异步调用的进度的 IAsyncResult。
委托对象.BeginInvoke();
EndInvoke
方法用于检索异步调用的结果,它可以在调用 BeginInvoke
之后的任意时间调用
如果异步调用尚未完成,那么 EndInvoke
将阻止调用线程,直到完成异步调用
---------------------------------------