C#编程语言之委托与事件(一)—— C/C++函数指针和C#委托初步
相信正在学习C#的人都有学习过C或C++的经验,本文要讲的第一个要点是C#中的委托(delegate,有些资料也叫代表)。什么是委托,很多人都能自然而然地想到C/C++中的函数指针,事实上很多书和资料都以此来引出C#中委托的概念,在此我建议如果没有接触过C/C++的同学可以先了解一下相关的知识再来继续C#的学习,毕竟作为编程语言的基础,语言都是招式,思维想法才是内功。有了扎实的基础,后期学习起来才能够事半功倍。
首先我们通过一个简单的例子快速复习一下C/C++函数指针:
#include<iostream>
using namespace std;
int func(string name){
cout<<"My name is "<<name<<endl;
}
void call(int(*fun)(string)){
fun("Evan Lin");
}
int main(int args,char ** argv){
call(func);
}
重点是 int(*fun)(string) 这个语句,指定一个函数指针的形参,就如同我们定义一个变量 char ch 一样,但要求是此处函数指针的返回值和参数列表都必须与即将传进来的函数地址严格匹配,不然会产生[Invalid Conversion Error],且此处的声明方式只能用指针,即这个 (*fun) ,因为事实上函数指针只是通过一个函数的入口去操作一个函数,虽然可以通过 typedef int (fun)(string); fun *fp 的方式去表示一个函数,但最终也是要定义一个函数的指针,所以此处无法不通过指针而去调用一个函数,至少目前阶段我没有了解到,有了解的朋友可以说出来共同探讨。
C#中的委托和C/C++中函数指针的对比
1、C/C++函数指针是通过寻找函数的入口来调用一个函数,C#委托是把函数名当做一个参数传入一个委托对象当中,委托是类型,函数指针是指针。
2、C/C++函数指针的返回类型和参数列表是作为匹配函数参数的标志,而C#委托有签名(Signature)的概念。
3、C/C++函数指针直接操作内存的某个地址,而C#委托托管在.Net Framwork下,是一种强类型
委托的签名(Signature)由委托的返回类型和参数列表组成,看起来和C/C++函数指针的返回类型和参数列表并无多大区别,但作为一门强大的语言,委托的签名的作用不仅仅是作为一种限定作用,其他作用会由下文提及。下面我们按部就班地一步步来认识委托(delegate)
一、下面是一则例子用于介绍委托的使用方法
using System;
namespace ConsoleApplication {
class DelegateTest {
public delegate void myDelegate(string name);
public static void func(string name) {
Console.WriteLine("My name is " + name);
}
static void Main() {
myDelegate _myDe = new myDelegate(DelegateTest.func);
_myDe("Evan Lin");
}
}
}
使用关键字delegate声明一个委托类型,声明形式主要是【delegate + 返回类型 + 委托名 + 参数列表】
像普通类型一样定义一个委托变量,生成委托对象时必须把签名相应的函数作为参数传入委托对象当中,然后进行调用。
二、委托的快捷语法,可以直接把函数名赋值给委托变量
myDelegate _myDe = DelegateTest.func;
_myDe("Evan Lin");
委托和函数(与签名相应)之间存在着隐式转换
三、多播(Multicast)委托
多播委托表示可以通过+=和-=的运算符号来添加或者删除到委托队列当中,当执行这个委托的时候会按依次执行添加到委托队列当中的所有委托,当使用多播委托时,委托的返回类型必须为void,否则运行时只会执行最后一个添加到委托队列的委托。
此处需要注意一点,当添加两个相同的函数时,在委托队列当中实质上添加了两个委托,但当减去一个委托时,如果该委托实体在委托队列中存在时,则把这份委托删除,但如果该委托实体在委托队列中不存在时,委托队列不做任何改变,且不会发生编译时异常。当委托队列为空,然后执行这个多播委托时,会抛出NullReferenceException。
下面看一小段代码加以理解:
using System;
namespace ConsoleApplication1 {
class DelegateTest {
public delegate void AnimalDelegate();
public static void Cat() {
Console.WriteLine("Miao Miao");
}
public static void Dog() {
Console.WriteLine("Wang Wang");
}
static void Main() {
AnimalDelegate _aniD;
AnimalDelegate _catD = new AnimalDelegate(DelegateTest.Cat);
AnimalDelegate _dogD = new AnimalDelegate(DelegateTest.Dog);
_aniD = _catD + _dogD;
_aniD();//Miao Miao \n Wang Wang
_aniD -= _catD;
_aniD();//Wang Wang
}
}
}
运行会依次打印“Miao Miao”和“Wang Wang”两行结果,然后再打印“Wang Wang”。
四、匿名方法和Lambda表达式
可以注意到使用委托真正起到作用的仅仅是委托的签名,为了提高开发效率,于是有了匿名方法(= =纯属猜想,欢迎斧正),具体实现方法如下:
using System;
namespace ConsoleApplication1 {
class DelegateTest {
public delegate String MyDelegate(int arg);
static void Main() {
MyDelegate _myDe = delegate (int arg) {
return arg > ? "More than zero" : "Less than or equals zero";
};
Console.WriteLine(_myDe());
Console.WriteLine(_myDe());
}
}
}
如代码所示,用【delegate关键字+参数列表+方法体】构成一个委托匿名方法,此处隐藏了具体的函数名称,匿名方法的返回值可有可无(根据委托签名),函数体的反花括号后要加分号,然后使用正常方法调用委托。说到这里相信大家都可以猜想到实际的输出结果,分别是Less than or equals zero和More than zero两行结果。
Lambda表达式具有比较特殊的写法,同样是为了提高开发效率,降低函数名的重复率等原因,以下通过一个实例进行了解:
using System;
namespace ConsoleApplication {
class DelegateTest {
public delegate String MyDelegate(int arg);
static void Main() {
MyDelegate _myDe = (arg) => {
return arg > ? "More than zero" : "Less than or equals zero";
};
}
}
}
实际效果等同于上一个匿名方法,在Lambda表达式中连参数类型都省去了,因为在定义一个委托类型的时候已经限定了委托的参数类型,以以上代码为例,其中参数arg的类型必须是int,返回类型必须是String。
五、委托泛型
如果对应于不同的函数返回类型和函数参数列表,需要声明大量不同签名的委托。泛型委托的出现是为了能适应不同类型的函数,提高代码的复用率,以下通过一个简单的例子来加深理解。
using System;
namespace ConsoleApplication {
class DelegateTest {
public delegate T1 myDelegate<T1, T2>(T1 arg1, T2 arg2);
public static string func1(string name,int num) {
return "My name is " + name + ",and my favorite number is " + num;
}
static void Main() {
myDelegate<string, int> _myDe = func1;
Console.WriteLine(_myDe("Evan Lin",));
}
}
}
其中的<T1,T2>代表两种自定义类型,同时分别作为委托的两种类型的参数,并且该委托返回T1类型的返回值。通过 myDelegate<string, int> _myDe 来限定<T1,T2>的具体类型。
当想定义一个泛型委托,但又想在类型方面做一些限制,可以用到where关键字
泛型委托约束大约包括几种形式:
public delegate T1 myDelegate<T1,T2>(T1 arg1,T2 arg2)where T1:ClassA
public delegate T1 myDelegate<T1,T2>(T1 arg1,T2 arg2)where T1:ClassA,InterfaceA
public delegate T1 myDelegate<T1,T2>(T1 arg1,T2 arg2)where T1:ClassA where T2:ClassB
public delegate T1 myDelegate<T1,T2>(T1 arg1,T2 arg2)where T1:T2
where后面的表达式代表类型T1只能派生于ClassA类或者是ClassA本身,T2同理。而ClassA,ClassB,InterfaceA等也有一些限制条件,官方文档的解释是:A type used as a constraint must be an interface,a non-sealed class or a type parameter。也就是说作为限制条件的只能是某个接口,非密封(non-sealed)类或者某个参数的类型(即第四句语句所示),除此之外的类型都不能作为泛型约束的类型,否则回显示Invalid constraint错误。
至此,以上均是个人学习C#委托时候的拙见,难免会有纰漏和不妥之处,欢迎指出斧正。
C#编程语言之委托与事件(一)—— C/C++函数指针和C#委托初步的更多相关文章
- Atitit java方法引用(Method References) 与c#委托与脚本语言js的函数指针
Atitit java方法引用(Method References) 与c#委托与脚本语言js的函数指针 1.1. java方法引用(Method References) 与c#委托与脚本语言js ...
- C语言函数指针与 c#委托和事件对比
C语言: 函数指针可以节省部分代码量,写类似具有多态的函数,比如要比较最大值,如果不用函数指针就只能写比较某一类型比如int类型的max函数,这个max无法比较string的大小.函数指针的意义就不多 ...
- VB6/VBA中跟踪鼠标移出窗体控件事件(类模块成员函数指针CHooker类应用)
一.关于起因 前几天发了一篇博文,是关于获取VB类模块成员函数指针的内容(http://www.cnblogs.com/alexywt/p/5880993.html):今天我就发一下我的应用实例. V ...
- .NET基础拾遗(4)委托、事件、反射与特性
Index : (1)类型语法.内存管理和垃圾回收基础 (2)面向对象的实现和异常的处理基础 (3)字符串.集合与流 (4)委托.事件.反射与特性 (5)多线程开发基础 (6)ADO.NET与数据库开 ...
- Silverlight项目笔记1:UI控件与布局、MVVM、数据绑定、await/async、Linq查询、WCF RIA Services、序列化、委托与事件
最近从技术支持转到开发岗,做Silverlight部分的开发,用的Prism+MVVM,框架由同事搭好,目前做的主要是功能实现,用到了一些东西,侧重于如何使用,总结如下 1.UI控件与布局 常用的主要 ...
- c# 关键字delegate、event(委托与事件)[MSDN原文摘录][1]
A delegate is a type that safely encapsulates a method, similar to a function pointer in C and C++. ...
- C#学习之初步理解委托、事件、匿名方法和Lambda
最经在学习LinqtoSql,然后扯到Lambda表达式,然后扯到匿名方法,然后扯到委托,最后扯到事件处理...后来发现对委托这个概念和事件处理这个过程理解得不是很清晰,遂得一下学习笔记.那里说得不对 ...
- C#委托及事件
转载:http://www.cnblogs.com/warensoft/archive/2010/03/19/1689806.html C#委托及事件 在C#中,委托(delegate)是一种引用类型 ...
- C# 基于委托的事件
事件基于多播委托的特性. 多播委托事实上就是一组类型安全的函数指针管理器,调用则执行顺序跳转到数组里所有的函数指针里执行. class Program { public class CarInfoEv ...
随机推荐
- JavaScript中的indexOf
JavaScript中的indexOf 1.JavaScript中利用indexOf拼接字符串 <%@ page language="java" import="j ...
- java特征
java的核心是面向对象,与之相对的是面向过程的编程,在对整个java编程没有足够的理解和运用的情况下恐怕没办法很好的理解这两个概念. 在我的初步理解中,写一个程序就例如做一件事情,面向过程的思想或许 ...
- TortoiseSVN设置忽略文件和目录文件夹
TortoiseSVN设置忽略文件和目录文件夹 在多数项目中你总会有文件和目录不需要进行版本控制.这可能包括一些由编译器生成的文件,*.obj,*.lst,或许是一个用于存放可执行程序的输出文件夹. ...
- Scrapyd日志输出优化
现在维护着一个新浪微博爬虫,爬取量已经5亿+,使用了Scrapyd部署分布式. Scrapyd运行时会输出日志到本地,导致日志文件会越来越大,这个其实就是Scrapy控制台的输出.但是这个日志其实有用 ...
- 【转】Java中hashCode的作用
以下是关于HashCode的官方文档定义: hashcode方法返回该对象的哈希码值.支持该方法是为哈希表提供一些优点,例如,java.util.Hashtable 提供的哈希表. hashCode ...
- canvas焰火特效
之前在抖音上看到了一个很漂亮的焰火效果.这会儿有时间就用canvas实现了一下. 演示地址:http://suohb.com/work/firework4.htm 先看效果:(静态图片看不太出效果,请 ...
- FFT \ NTT总结(多项式的构造方法)
前言.FFT NTT 算法 网上有很多,这里不再赘述. 模板见我的代码库: FFT:戳我 NTT:戳我 正经向:FFT题目解题思路 \(FFT\)这个玩意不可能直接裸考的..... 其实一般\(FF ...
- 如何巧妙的利用selenium和requests组合来进行操作需要登录的页面
一.在这里selenium的作用 (1)模拟的登录. (2)获取登录成功之后的cookies 代码 def start_login(self): chrome_options = Options() ...
- Developer Survey Results 2017
概观 今年,超过64,000名开发人员告诉我们他们学习和升级的方式,他们使用的工具和他们想要的东西. 自2011年以来,Stack Overflow每年都会向开发者询问他们最喜爱的技术,编码习惯,工作 ...
- [.Net Core] 简单使用 Mvc 内置的 Ioc
简单使用 Mvc 内置的 Ioc 本文基于 .NET Core 2.0. 鉴于网上的文章理论较多,鄙人不才,想整理一份 Hello World(Demo)版的文章. 目录 场景一:简单类的使用 场景二 ...