详解C#委托和事件(一)
委托(Delegate)是安全封装方法的类型,类似于C和C++中的函数指针,与函数指针不同的是,委托是面向对象的、类型安全的和可靠的;
一、委托类型是CTS中五种基础类型之一,是一种引用类型,表示对具有指定参数列表和返回类型的方法的引用,也是一种特殊的类类型,其类型为System.MulticastDelegate,继承自抽象类System.Delegate;使用委托类型声明的对象,即委托实例,是一种特殊类型的对象,其可以与任何该委托所能封装的方法相关联,然后通过委托实例调用这些方法;
※在命名空间下声明的委托类型与类类型相似,只能为public或internal,在类内部声明的委托类型为当前类的嵌套类型,可以指定各种访问修饰符;在类内部声明的委托实例是一个字段,在方法内部声明的委托实例是一个局部变量;
1.委托的声明与类的声明方式相似,委托的类型由声明委托的名称确定:
//声明一个自定义的委托类型MyDelegate,可以封装无参数列表,无返回值的方法
public delegate void MyDelegate();
2.每个委托类型都描述其所能封装的方法的参数列表(参数数目和类型)以及返回值类型,每当需要封装一组新的参数列表或返回值类型的方法时,都必须声明一个新的委托类型;
委托实例的声明与对象的声明方式相似,由委托类型和委托实例的名称组成:
//声明一个MyDelegate类型的委托实例myDelegate
private MyDelegate myDelegate;
3.声明和实例化委托时,可以使用new关键词、直接用方法名赋值、匿名方法或者Lambda表达式;
4.委托的一个重要特性是可以使用+或+=运算符将多个具有相同参数列表和返回值类型的方法或相同类型的委托实例添加到一个委托实例中,使用-或-=运算符可以从委托实例中移除指定的方法或委托实例,调用该委托实例时,会调用其中的所有方法,这个特性被称为多播委托(Multicast Delegates),广泛应用于事件中;
多播委托内包含已赋值委托的列表,在调用多播委托时,它会按顺序调用列表中的委托;对于有返回值的多播委托,其返回值是按顺序执行列表后最后一个方法的返回值;
public delegate void MyDelegate();
public class MyClass
{
public void MyFunc()
{
Console.WriteLine("MyFunc Run");
}
}
public class Program
{
static void Main(string[] args)
{
MyClass myClass = new MyClass();
MyDelegate myDelegate;
//1.使用new创建委托对象时指定方法
myDelegate = new MyDelegate(myClass.MyFunc); //先用=运算符创建实例,=后面同样可以使用以下任何一种方式进行赋值
//2.直接用方法名赋值
myDelegate += myClass.MyFunc;
//3.使用匿名方法赋值
myDelegate += delegate ()
{
Console.WriteLine("Anonymous methods run");
};
//4.使用Lambda表达式赋值
myDelegate += () =>
{
Console.WriteLine("Lambda run");
};
//5.使用相同类型的委托实例赋值
myDelegate += new MyDelegate(myClass.MyFunc);
//6.使用-=运算符删除委托中指定的方法
myDelegate -= myClass.MyFunc; //※只能通过此种方式才可以将委托列表中指定方法移除,不能通过myClass = null等方式移除 //执行委托中的方法
myDelegate();
//或者通过其Invoke()方法执行委托
//myDelegate.Invoke();
//增加非空判断
//if (myDelegate != null) myDelegate();
//或者简化为(需要C#6.0以上)
//myDelegate?.Invoke(); Console.ReadKey();
}
}
※可以将类或结构中任何与委托类型的参数列表和返回值类型相匹配的实例方法和静态方法赋值给委托实例;
※使用匿名方法或Lambda表达式添加的方法不能通过-或-=删除这些方法;
※与直接调用方法相比,使用委托调用方法用时几乎没有差别,使用以下代码多次测试:
public delegate void MyDelegate();
public class MyClass
{
public void MyFunc()
{ }
} class Program
{
static void Main(string[] args)
{
MyClass myClass = new MyClass();
MyDelegate myDelegate = myClass.MyFunc; Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = ; i < 10_000_000; i++)
{
myDelegate();
}
stopwatch.Stop();
Console.WriteLine($"使用委托调用1千万次耗时:{stopwatch.ElapsedMilliseconds}"); stopwatch.Reset();
stopwatch.Start();
for (int i = ; i < 10_000_000; i++)
{
myClass.MyFunc();
}
stopwatch.Stop();
Console.WriteLine($"直接调用1千万次耗时:{stopwatch.ElapsedMilliseconds}"); Console.Read();
}
}
二、委托实例是一种特殊类型的对象,其特殊之处在于,其他对象都包含数据,而委托实例中只包含一个或多个方法的引用,也正是因为这个特性,通常用委托实例来传递方法,即通过委托实例作为载体将方法作为参数传递到另一个方法中,并在方法中的某个时机或稍后调用委托实例来执行这个方法,这被称为异步回调,作为参数的这个方法也叫回调函数(Callback Method);
1.所有方法都可以直接隐式转换为对应参数列表和返回值类型的委托:
public void MyFunc(Action<int> myAction, int myNum)
{
if (myAction != null)
{
myAction(myNum);
}
}
public void MyAction(int myNum)
{
Console.WriteLine("myNum is : " + myNum);
}
//使用方式:
MyFunc(MyAction, ); //myNum is : 1
2.方法不能显示转换为object类型,但委托可以,所以可以将方法显示转换为委托然后作为参数传递:
public void MyFunc(object myObj, int myNum)
{
Action<int> myAction = (Action<int>)myObj;
myAction(myNum);
}
public void MyAction(int myNum)
{
Console.WriteLine("myNum is : " + myNum);
}
//使用方式:
MyFunc((Action<int>)MyAction, ); //myNum is : 1
三、对于有返回值的多播委托,如果想保存其返回值列表,可以先获取委托实例中的方法,然后依次执行这些方法:
Func<string> myFunc;
Delegate[] delegateArray = myFunc.GetInvocationList();
string[] returnArray = new string[delegateArray.Length];
for (int i = ; i < delegateArray.Length; i++)
{
returnArray[i] = (delegateArray[i] as Func<string>)();
//returnArray[i] = (string)delegateArray[i].DynamicInvoke();
}
四、引用System命名空间后,可以使用系统定义的两个泛型委托void Action()和TResult Func<TResult>(),其中,Action()为无返回值的委托,Func<TResult>()为有返回值且返回值类型为TResult的委托,两者都可以拥有多个参数,例如:void Action<T1, T2>(T1 obj1, T2 obj2)和TResult Func<T1, T2, TResult>(T1 obj1, T2 obj2);
1.声明一个多个参数的委托类型:
public Action<string, int> MyAction; //声明一个无返回值,参数列表为string,int的委托
public Func<bool, string, int> MyFunc; //声明一个返回值类型为bool,参数列表为string,int的委托
2.System命名空间还提供了一个返回bool值的委托bool Predicate<T>(T obj),通常用于匹配相关的操作;
五、事件(Event)是一种具有一定限制的特殊的多播委托:
//首先声明一个委托或使用系统预留的委托类型Action等
public delegate void MyDelegate();
//声明事件,相比声明委托实例多了一个event关键字
public event MyDelegate MyEvent;
1.委托和事件的关系类似变量和属性的关系,委托可以作为局部变量,也可以作为类的成员,而事件只能作为类的成员;委托可以在任何地方进行赋值(=)和调用操作,而事件仅可以从声明事件的类或结构中对其进行赋值和调用操作,在外部只能进行+=添加方法和-=移除方法操作;
2.通常,委托用于回调,将方法当做参数传递到其他方法中,并在指定的时机调用该委托中的方法;事件用于通知,在类或对象中通知其他类或对象以进行相关操作,即接收方将需要响应的方法注册到源对象的事件上,当源对象发生了某个特定情况时,触发该事件,此时该事件所注册的所有方法都会被调用;
public class MyClass
{
public delegate void MyDelegate();
public event MyDelegate MyEvent; public void OnEvent()
{
//只可在当前类中进行调用
MyEvent?.Invoke();
//只可在当前类中进行赋值
//MyEvent = null;
}
}
//使用方式:
MyClass myClass = new MyClass();
//在外部使用+=给事件添加方法
myClass.MyEvent += MyFunc; //方法定义:void MyFunc() { Console.WriteLine("MyFunc run"); }
myClass.OnEvent(); //MyFunc run
3.事件中方法的添加和移除是通过事件访问器实现的,其形式类似属性访问器,不同之处在于事件访问器命名为add和remove,在默认情况下,不需要手动定义事件访问器,编译器会自动添加默认的事件访问器,也可以进行自定义事件访问器:
private event MyDelegate myEvent;
public event MyDelegate MyEvent
{
add { myEvent += value; }
remove { myEvent -= value; }
}
4.在使用反射获取类型的所有方法时,如果类型中包含事件,会获取事件中的公共访问器所生成的方法:
typeof(MyClass).GetMethods(); //add_MyEvent remove_MyEvent ToString Equals GetHashCode GetType
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的认可是我写作的最大动力!
作者:Minotauros
出处:https://www.cnblogs.com/minotauros/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
详解C#委托和事件(一)的更多相关文章
- 详解C#泛型(二) 获取C#中方法的执行时间及其代码注入 详解C#泛型(一) 详解C#委托和事件(二) 详解C#特性和反射(四) 记一次.net core调用SOAP接口遇到的问题 C# WebRequest.Create 锚点“#”字符问题 根据内容来产生一个二维码
详解C#泛型(二) 一.自定义泛型方法(Generic Method),将类型参数用作参数列表或返回值的类型: void MyFunc<T>() //声明具有一个类型参数的泛型方法 { ...
- 详解C#委托,事件与回调函数
.Net编程中最经常用的元素,事件必然是其中之一.无论在ASP.NET还是WINFrom开发中,窗体加载(Load),绘制(Paint),初始化(Init)等等.“protected void Pag ...
- 详解C#委托和事件(二)
一.当我们使用关键字delegate声明一个自定义委托类型时,实际上是声明了一个该名称的类类型,继承自抽象类System.MulticastDelegate,还包含实例方法Invoke.BeginIn ...
- jQuery:详解jQuery中的事件(二)
上一篇讲到jQuery中的事件,深入学习了加载DOM和事件绑定的相关知识,这篇主要深入讨论jQuery事件中的合成事件.事件冒泡和事件移除等内容. 接上篇jQuery:详解jQuery中的事件(一) ...
- 详解Vue 方法与事件处理器
本篇文章主要介绍了详解Vue 方法与事件处理器 ,小编觉得挺不错的,现在分享给大家,也给大家做个参考.一起跟随小编过来看看吧 方法与事件处理器 方法处理器 可以用 v-on 指令监听 DOM 事件 ...
- ALSA声卡驱动中的DAPM详解之七:dapm事件机制(dapm event)
前面的六篇文章,我们已经讨论了dapm关于动态电源管理的有关知识,包括widget的创建和初始化,widget之间的连接以及widget的上下电顺序等等.本章我们准备讨论dapm框架中的另一个机制:事 ...
- jQuery:详解jQuery中的事件(一)
之前用过一些jQuery的动画和特效,但是用到的部分也不超过10%的样子,感觉好浪费啊——当然浪费的不是jQuery,而是Web资源.后来就想深入研究下jQuery的内部机理,读过两遍jQuery源代 ...
- localStorage、sessionStorage详解,以及storage事件使用
有关localStorage和sessionStorage的特性. localStorage本身带有方法有 添加键值对:localStorage.setItem(key,value),如果key存在时 ...
- 【C#】详解C#委托
目录结构: contents structure [+] 委托语法 泛型委托 委托链 lambda表达式 揭秘委托 类库中的委托 委托和反射 1.委托语法 本文会详细阐述委托的使用,以及实现,想必读者 ...
随机推荐
- 对话框的按键处理 PreTranslateMessage、OnKeyDown和OnChar
对话框的按键处理 PreTranslateMessage.OnKeyDown和OnChar 1.MFC对话框不能响应OnKeyDown和OnChar函数(1)现象 在MFC的对话框中,映射了WM_C ...
- C#数组的定义,不定长的数组?
首先,在这里我要说明的是,C#中,定义了数组,那么就必须为其指定长度,且他的长度确定,不能够更改.一旦定义一个数组,那么操作系统就在内存中给这个数组指定了一块内存,他是不支持动态分配存储空间的.能够动 ...
- ASP.NET MVC 全局异常
先新建一个过滤器ExceptionHandleErrorAttribute.cs 内容如下: using System; using System.Net; using System.Web; usi ...
- Entity Framework学习记录
记录一次ef code first的学习记录 最近想做一套自己的框架,正在寻找合适的ORM,之前参照力软(很早之前的版本了)的底层代码,做了一套自己的增删改查, 但是使用起来总觉得缺了点什么? 所以决 ...
- 安装docker ce版
可参考 菜鸟教程:http://www.runoob.com/docker/centos-docker-install.html 官网教程:https://docs.docker.com/instal ...
- UWP开发---通过委托跨页面导航
-前言 做过.Net开发的都了解,当二级窗口操作主窗口的控件时通常用委托的方式.那么在UWP开发中,常常会遇到MainPage的二级Frame里面的内容去操作MainPage的导航跳转,具体看下图: ...
- JQuery Mobile - 修改复选框的选中状态无效解决办法!
今晚,在编写JQuery Mobile程序时候,需要在代码里面控制复选框的选中状态,很简单的代码啊,很快完成了!等测试程序时候傻眼了,页面无论如何也不按照我写的代码显示出来!问题出在哪里呢?是我写的控 ...
- xgboost 和GBDT的区别
作者:wepon链接:https://www.zhihu.com/question/41354392/answer/98658997来源:知乎 传统GBDT以CART作为基分类器,xgboost还支持 ...
- 975. Odd Even Jump
You are given an integer array A. From some starting index, you can make a series of jumps. The (1 ...
- jzoj5804
這道題n-m很小,可以從此入手 記f[i][j]為i個字符括號綜合為j的合法方案數 則第i個括號可以枚舉為(和),所以f[i][j]=f[i-1][j-1]+f[i-1][j+1],小心越界 再記a為 ...