C#委托、事件剖析(上)
本节对委托、事件做以总结。
一、委托:
1、概念:先来说明变量和函数的概念,变量,以某个地址为起点的一段内存中所存储的值,函数,以某个地址为起点的一段内存中存储的机器语言指令。有了这2个概念以后,我们来看c++中的函数指针,函数指针就是指向这个函数的地址,函数指针所指向的类型就是函数在内存中的大小,有了这个起点和大小,函数指针就可以代替函数完成对函数的调用。在C#中,委托delegate就是对c++中函数指针做了一个升级,同样它没有直接调用方法采用的是间接调用,是一种类,所以也是一种数据类型。下面举一个简单的例子,说明它是类。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Action action = new Action(Method);
Console.WriteLine(action.GetType().IsClass);
}
static void Method()
{ }
}
}

这里我们用了C#类库中自带的Action委托,先不需要管它是什么样的委托,后面会介绍,然后调用Type类的IsClass属性,返回true,则他就是一个类,所以它也是一种数据类型。可以看出,委托形成了一种动态调用代码(方法)的结构,功能十分强大。
2、委托的一般使用:在声明一个委托时,这个委托的参数就是一个方法名,这样就可以把这个具体的委托当做参数传入另一个方法,也就相当于把
这个委托中的方法当做参数传入另一个方法,这个被传入的方法分为2种:
(1)回调方法:无返回值,没有返回值就说明他只是做了一些处理,至于被不被调用完全要看主调方法是否选择调用它,这就和找工作一个道理,你发一份简历
出去,至于公司给不给你offer取决于公司。
(2)模(mu)板方法:有返回值,说明你所返回的东西会对调用者起一定的影响作用,有返回值一般也有参数,根据参数的不同返回不同的返回值,所以
它的作用对于调用者是一个模板。
Example1:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Action action = new Action(Method);
action.Invoke();
action(); }
static void Method()
{
Console.WriteLine("Hello Delegate");
}
}
}

这里用的是C#类库中最常用的返回值为空并且无参的Action委托,Method方法无参无返回值,2种调用方式,第一种调用委托的invoke()方法,
第二种采用的是函数指针式的调用,都可以使用。
Example2:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
Action<string, int> action = new Action<string, int>(Method);
action.Invoke("张三",);
action("李四",);
}
static void Method(string name, int age)
{
Console.WriteLine($"我叫{name}今年{age}岁");
}
}
}

这里用到了C#自带的常见泛型委托Action<T>无返回值有参数,泛型这里就当做一个类型就好,会在别的章节做详细说明
Example3:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
Func<double, double, double> func = new Func<double, double, double>(Add);
double result1 = func.Invoke(1.5,3.5);
Console.WriteLine(result1);
double result2 = func(2.5,4.5);
Console.WriteLine(result2);
}
static double Add(double x, double y)
{
double result = x + y;
return result;
} }
}
这里用了C#类库中常用的Func<T>委托,也是一个泛型委托,<>中最后一个是返回值结果,可以在vs的提示重载中看到。
Example4:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
Func<string> func = new Func<string>(FindName);
SayHello(func);
}
static void SayHello(Func<string> FindDelegate)
{
Console.WriteLine($"Hello {FindDelegate()}");
}
static string FindName()
{
return "小明";
} }
}
这里用了Func<T>只有返回值的情况,并将这个委托当做参数传进了另一个方法,也就间接的把FindName这个方法当做参数传入了SayHello这个方法。
3、下面举两个比较贴近生活、委托和别的结合使用的典型事例。
Example1:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace ConsoleApp3
{
public delegate Product ProductDelegate();
public delegate void LogDelegate(Product product);
class Program
{
static void Main(string[] args)
{ WrapProduct product = new WrapProduct();
Logger logger = new Logger();
WrapFactory wrapFactory = new WrapFactory();
ProductDelegate productDelegate1 = new ProductDelegate(product.GetToy);
ProductDelegate productDelegate2 = new ProductDelegate(product.GetStationery);
LogDelegate logDelegate = new LogDelegate(logger.Log);
Box box1 = wrapFactory.GetBox(productDelegate1,logDelegate);
Box box2 = wrapFactory.GetBox(productDelegate2,logDelegate);
Console.WriteLine($"Product1 {box1.Product.Name} the price is {box1.Product.Price}");
Console.WriteLine($"Product2 {box2.Product.Name} the price is {box2.Product.Price}"); } } public class Product
{
public string Name { get; set; }
public int Price { get; set; }
}
public class Box
{
public Product Product { get; set; }
}
public class WrapFactory
{
public Box GetBox(ProductDelegate productDelegate,LogDelegate logDelegate)
{
Box box = new Box();
Product product = productDelegate.Invoke();
if (product.Price>)
{
logDelegate.Invoke(product);
}
box.Product = product;
return box;
}
}
public class WrapProduct
{
public Product GetToy()
{
Product product = new Product();
product.Name = "Toy";
product.Price = ;
return product;
}
public Product GetStationery()
{
Product product = new Product();
product.Name = "Stationery";
product.Price = ;
return product;
}
}
public class Logger
{
public void Log(Product product)
{
Console.WriteLine($"Product {product.Name} created at {DateTime.Now.ToString()}");
}
}
}

delegate既然是类,那么应该和类平级,放于类的外部。这里用了自定义的委托,有返回值的委托ProductDelegate封装的方法是WrapProduct制造产品类里的制造玩具和制造文具方法,无返回值的委托LogDelegate封装的是Logger记录日志类里的Log日志方法。首先做2个实体类,Product产品类,Box盒子类,盒子中放的就是产品,然后做一个包装类,返回一个盒子,写一个将产品包装在盒子中的方法,这个方法的2个参数,是2个委托,一个用于创作产品一个当产品价格大于50的时候,就调用log方法记录日志,最后在main方法里开始实例化类并调用,自定义委托和C#类库自带的委托都可以使用,看个人喜好,C#自带的就不用声明委托了。
Example2:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace ConsoleApp4
{
class Program
{
static void Main(string[] args)
{
Func<int, int, int> func1 = new Func<int, int, int>((int a, int b) => { return a + b; });
var func2 = new Func<int,int,int>((a, b) => { return a * b; });
Calculate(func1,,);
Calculate(func2,,);
Calculate((x,y)=> { return x - y; },,);
}
static void Calculate<T>(Func<T,T,T> func,T x,T y)
{
T z = func.Invoke(x,y);
Console.WriteLine(z);
}
}
}
这里用到了蛮多的小知识点,首先泛型函数和泛型委托,然后用到了lambda表达式,精简的说一下lambda表达式:(T t)=>{expression; }小括号里是参数,大括号中是要写的算法,也就是方法体,当然不会写太多,不然还不如写一个方法就用不到lambda表达式了。已经知道,委托声明是封装一个方法,那么就可以用lambda表达式代替方法,这就是把一个lambda表达式赋值给一个委托,C#中很多委托都会用到,所以第三次调用Calculate方法时,直接将lambda表达式当成参数传进去,是不会报错的。最后还有一个重要的点,就是泛型委托的类型参数推断,在第二个委托func2中,C#根据传入的参数推断出泛型的具体类型是int,从而将代码简写。
4、委托的抗变和协变
1、概念: .net 4.0中抗变和协变已经成熟了,主要分为2类,委托的和泛型的,此处只讲委托的,泛型的后面会说明。委托所封装的方法和声明委托是所定义的类型不一定相同,这就产生了抗变和协变。
namespace ConsoleApp5
{
class Father { }
class Son : Father { }
class Program
{
static void Main(string[] args)
{
Func<Father> func = new Func<Father>(Method);
}
static Son Method()
{
Son son = new Son();
return son;
}
}
}
上面的是抗变:2个实体类Father父类,Son子类,继承Father,委托声明时,返回值为父类,调用的时候却调用的是返回值为Son的方法,
也就是说抗变指的是委托所封装的方法的返回值是声明委托的返回值类型的子类。
namespace ConsoleApp5
{
class Father { }
class Son : Father { }
class Program
{
static void Main(string[] args)
{
Action<Son> action = new Action<Son>(Method);
}
static void Method(Father father) { }
}
}
现在这个自然是协变,仍然一个父类一个子类,很明显,协变指的是委托所封装的方法的参数是声明委托时参数的父类。
5、委托的高级使用:
主要讲2个方面:多播委托以及委托的隐式异步调用。
(1)多播委托:
通常的委托,一个委托封装一个方法,多播委托中可以一个委托封装多个方法,这些方法通常都是void的,但是不为空也可以,不会报错,实例如下:
namespace ConsoleApp6
{
class Program
{
static void Main(string[] args)
{
var func = new Func<int, int, int>(M1);
func += M2;
func += M3;
func(,);
Console.WriteLine(func(,));
}
static int M1(int x, int y)
{
Console.WriteLine(x+y);
return x + y;
}
static int M2(int x, int y)
{
Console.WriteLine(x - y);
return x - y;
}
static int M3(int x, int y)
{
Console.WriteLine(x*y);
return x * y;
}
}
}

可以看到确实没有报错,但是它最后的返回值是9,也就是说调用多播委托以后他最后的返回值是最后一个方法的返回值,所以有返回值的方法一般不用于多播委托,来看一个正常的例子。
namespace ConsoleApp7
{
class Program
{
static void Main(string[] args)
{
var action = new Action(M1);
action += M2;
action += M3;
action.Invoke();
action -= M2;
action.Invoke();
}
static void M1()
{
Console.WriteLine("M1 is invoked");
}
static void M2()
{
Console.WriteLine("M2 is invoked");
}
static void M3()
{
Console.WriteLine("M3 is invoked");
}
}
}

这里用+=和-=将方法逐一封装在同一个委托里,实现了只需要调用一次委托就调用了所有方法的功能。
那这里的底层实现是什么呢,先举实例:
namespace ConsoleApp8
{
class Program
{
static void Main(string[] args)
{
var action1 = new Action(M1);
var action2 = new Action(M2);
var action3 = new Action(M3);
Action action = null;
action = (Action)Delegate.Combine(action,action1);
action = (Action)Delegate.Combine(action,action2);
action = (Action)Delegate.Combine(action,action3);
action();
Console.WriteLine();
action = (Action)Delegate.Remove(action,action2);
action();
}
static void M1()
{
Console.WriteLine("M1 is invoked");
}
static void M2()
{
Console.WriteLine("M2 is invoked");
}
static void M3()
{
Console.WriteLine("M3 is invoked");
}
}
}

从上面的例子中可以看出,+=和-=的具体实现使用Delegate的Combine和Remove方法,来增加或删除委托中的方法。
(2)先来看一下显示异步调用:
namespace ConsoleApp9
{
class Program
{
static void Main(string[] args)
{
Thread thread1 = new Thread(new ThreadStart(M1));
Thread thread2 = new Thread(new ThreadStart(M2));
thread1.Start();
thread2.Start();
}
static void M1() {}
static void M2() {}
}
}

这里可以用到了线程,可以看到ThreadStart是一个委托。
namespace ConsoleApp10
{
class Program
{
static void Main(string[] args)
{
Task task = new Task(new Action(M1));
task.Start();
}
static void M1() { }
}
}
这里用Task也可以,也是线程中的东西关于task会在以后详细说明,可以看到参数也是一个委托。
下面是隐式异步调用的例子:
namespace ConsoleApp11
{
class Program
{
static void Main(string[] args)
{
Calculator calculator = new Calculator();
Func<int, int, int> func = new Func<int, int, int>(calculator.Add);
IAsyncResult asyncResult = func.BeginInvoke(,,null,null);
Console.WriteLine($"结果是{func.EndInvoke(asyncResult)}");
Console.WriteLine("计算完成");
}
} public class Calculator
{
public int Add(int a, int b)
{
return a + b;
}
}
}
隐式异步调用的底层机制就是多线程,而委托中的BeginInvoke方法恰好会生成分支线程,所产生的信息可以通过IAsyncResult接受,产生的返回值调用EndInvoke方法即可,注:BeginInvoke方法的参数是一个AsyncCallBack委托,主要用来作为回调函数也就是你调完方法以后还需要做什么,如果不需要传入null就可以了。
到此委托部分结束,事件会在下一节总结。 2018-08-17 10:31:56
C#委托、事件剖析(上)的更多相关文章
- C#基础加强篇----委托、Lamada表达式和事件(上)
1.委托 C#的委托相当于C/C++中的函数指针.函数指针用指针获取一个函数的入口地址,实现对函数的操作. 委托与C/C++中的函数指针不同在于,委托是面向对象的,是引用类型,对委托的使用要先定义后实 ...
- Observer设计模式中-委托事件-应用在消息在窗体上显示
Observer设计模式:监视者模式.在类中的方法中处理的结果或者消息通过事件委托 的方式发送给主窗体. 因为在其它类中直接访问主窗体类,显示内容是不能直接调用控件赋值的,当然也有别的类似查阅控件名, ...
- C#委托,事件理解入门 (译稿)
原文地址:http://www.codeproject.com/Articles/4773/Events-and-Delegates-Simplified 引用翻译地址:http://www.cnbl ...
- 关于ios使用jquery的on,委托事件失效
$('.parents').on("click",'.child',function(){}); 类似上面这种,在ios上点击"child"元素不会起作用,解决 ...
- jQuery里面的普通绑定事件和on委托事件
以click事件为例: 普通绑定事件:$('.btn1').click(function(){}绑定 on绑定事件:$(document).on('click','.btn2',function(){ ...
- c#委托事件入门--第二讲:事件入门
上文 c#委托事件入门--第一讲:委托入门 中和大家介绍了委托,学习委托必不可少的就要说下事件.以下思明仍然从事件是什么.为什么用事件.怎么实现事件和总结介绍一下事件 1.事件是什么:. 1.1 NE ...
- JavaScript中事件委托(事件代理)详解
在JavaScript的事件中,存在事件委托(事件代理),那么什么是事件委托呢? 事件委托在生活中的例子: 有三个同事预计会在周一收到快递.为签收快递,有两种办法:一是三个人在公司门口等快递:二是委托 ...
- python 全栈开发,Day55(jQuery的位置信息,JS的事件流的概念(重点),事件对象,jQuery的事件绑定和解绑,事件委托(事件代理))
一.jQuery的位置信息 jQuery的位置信息跟JS的client系列.offset系列.scroll系列封装好的一些简便api. 一.宽度和高度 获取宽度 .width() 描述:为匹配的元素集 ...
- jQuery Direct and delegated events 直接事件与委托事件
ref: http://api.jquery.com/on/ 直接事件: 将事件委托直接绑定到dom元素上,当事件发生时触发handler. 委托事件: 将事件委托绑定到dom元素的外层容器上,当事 ...
随机推荐
- struts框架问题六之从值栈中获取值
6. 问题六: 在JSP中获取值栈的数据 * 总结几个小问题: > 访问root中数据 不需要# > 访问context其它对象数据 加 # > 如果向root中存入对象的话,优先使 ...
- 基于udp的套接字
1 ss = socket() #创建一个服务器的套接字 2 ss.bind() #绑定服务器套接字 3 inf_loop: #服务器无限循环 4 cs = ss.recvfrom()/ss.send ...
- ThinkPHP5路由图解
- Task构造
//原文:http://www.tuicool.com/articles/IveiQbQ 创建并且初始化Task 使用lambda表达式创建Task Task.Factory.StartNew(() ...
- 用pyqt5做一个能python程序能插入图片的ide
之前只是放到github上了,现在一想应该开源,大家想继续做好这个ide的都能从这里起步. #注意在.py文件相同目录下放一个1.png做测试图片 #本质就是用html来实现图片 #写在前面的话:这个 ...
- centos6.5 设置ssh无密码登录
:关闭防火墙 vim /etc/selinux/config 把SELINUX=enforcing修改为SELINUX=disabled A机器root连接B机器root用户 (root用户登录) ...
- 2018.07.22 洛谷P2986 伟大的奶牛聚集(树形dp)
传送门 给出一棵树,树有边权和点权,若选定一个点作为中心,这棵树的代价是所有点权乘上到根的距离的和.求代价最小. 解法:一道明显的换根dp" role="presentation& ...
- Scrapy学习篇(十三)之scrapy-splash
之前我们学习的内容都是抓取静态页面,每次请求,它的网页全部信息将会一次呈现出来. 但是,像比如一些购物网站,他们的商品信息都是js加载出来的,并且会有ajax异步加载.像这样的情况,直接使用scrap ...
- KindEditor解决上传视频不能在手机端显示的问题
KindEditor自带的上传视频生成的HTML代码为<embed>,在手机端并不支持.于是可以自己在控件里增加生成video标签相关代码. 参考https://www.jianshu.c ...
- hfs快速搭建HTTP文件服务器
HFS全称为http file server,他是一个专门实现文件共享的工具,通过WWW服务将要共享的目录或文件发布,从而让需要下载者直接通过IE浏览器访问发布的这个共享站点,然后随意下载共享资源.和 ...