最近一直遇到c#的委托,刚开始看的云里雾里的,后来以c++的函数指针来类比就醍醐灌顶了。所以特此记载下来。
因为最先接触的是c++,所以先来看c++的函数指针,再来讲c#的委托。
参考c++primer Plus 第6版
函数有地址,函数的地址是存储其机器语言代码的内存的开始地址。
函数地址:函数名。比如说Test()是一个函数,那么Test就是这个函数的地址。要将函数作为参数进行传递,必须传递函数名。
声明函数指针:声明指向某种数据类型的指针时,必须指定指针指向的类型。同样,声明指向函数的指针时,也必须指定指针指向的函数类型。比如,函数原型如下:
double Killer(int);
那么正确的函数指针声明:
double (*kl)(int);
你看,这两个声明是不是很像,第一个是函数声明,第二个其实也相当于函数声明,只不是Killer是函数,(*kl)也是函数,kl是函数指针。所以,正确声明kl之后,我们就可以把函数地址赋给它了:
kl=Killer
注意:符号优先级在这里格外重要。比如:
*kl(int) //意味着kl()是一个返回指针的函数
(*kl)(int) //意味着kl是一个指向函数的指针
在函数地址赋给函数指针成功以后,我们就可以用函数指针调用函数了。
e.g.
#include
using namespace std;
double betsy(int);
double pam(int);
void estimate(int lines,double (*pf)(int));
int main(){
int code;
cout<<"How many lines of code do you need? ";
cin>>code;
cout<<"Here's Betsy's estimate:\n";
estimate(code,betsy);
cout<<"Here's pam's estimate:\n";
estimate(code,pam);
return 0;
}
double betsy(int lns){
return 0.05*lns;
}
double pam(int lns){
return 0.03*lns+0.004*lns*lns;
}
void estimate(int lines,double (*pf)(int)){
cout<
参考博客如下:
https://docs.microsoft.com/zh-cn/dotnet/csharp/tour-of-csharp/delegates
https://blog.csdn.net/dingxiaowei2013/article/details/18428727
https://blog.csdn.net/SerenaHaven/article/details/80047622
参考c#高级编程第8版
委托的概念:在c++编程中,只能提取函数地址,并作为一个参数传递它。但是这种方法的缺陷就是,在面向对象编程时,几乎没有方法是孤立存在的,而是在调用方法前通常需要把类实例相关联。所以.NET Framework在语法上不允许使用这种直接方法,如果要传递方法,就必须把方法的细节封装在一种新类型的对象中,即委托。委托只是一种特殊类型的对象,其特殊之处在于,我们以前定义的所有对象都包含数据,而委托包含的只是一个或多个方法的地址。
emmm,是不是感觉看不透。没关系,慢慢接着看,然后再回来多读几遍。
怎么使用委托呢?和类的使用过程是一样的。 1.定义委托 2.实例委托
定义委托的语法如下:
delegate void IntMethodInvoker(int x); //定义一个委托IntMethodInvoker,并指定该委托的每个实例都可以包含一个方法的引用,该方法带有一个int参数,并返回void
delegate double TwoLongsOp(long first,long second); //定义一个委托TwoLongsOp,并指定该委托的每个实例都可以包含一个方法的引用,该方法带有两个long参数,并返回double类型
delegate string GetAString(); //定义一个委托GetAString,并指定该委托的每个实例都可以包含一种方法的引用,该方法没有参数,返回为string类型
注意:类有两个不同的术语,“类”表示较广义的定义,“对象”表示类的实例。但是委托只有一个术语。在创建委托的实例时,所创建的委托的实例仍然成为委托。
e.g.
using System;
class MathOperations
{
public static double MultiplyByTwo(double value)
{
return value * 2;
}
public static double Square(double value)
{
return value * value;
}
}
namespace Demo
{
//定义委托
delegate double DoubleOp(double x);
class Program
{
static void Main()
{
//实例化一个委托数组,该数组的每个元素都初始化为由MathOperations类实现的不同操作
DoubleOp[] operations =
{
MathOperations.MultiplyByTwo,
MathOperations.Square
};
//遍历委托数组,调用不同的方法
for(int i = 0; i
在上面的那个程序中:
所以委托、类、方法之间的情况,一定要理解清楚咯。我的理解就是委托其实和类的使用是差不多的,都需要定义和实例化,但是委托和方法又离的很近很近,它可以直接代表方法,并且作为参数使用(这点又和函数指针很像)。
注意:在这里只是申请了委托数组,并不是多播委托。多播委托将在后面讲述。
随着c#的版本更新,出现了更加实用的委托方式。就是Action
Action
下面以BubbleSorter(冒泡排序)为例,讲解一下委托的优势。
在学习到委托之前,一说到冒泡排序,大脑还没思考,手就开始机械化撸代码了。一般都是这么写的:
for(int i=0;isortArray[i+1])
{
int temp = sortArray[i];
sortArray[i]=sortArray[i+1];
sortArray[i+1]=temp;
}
}
对吧,这是程序员必修课了基本上,闭着眼也能撸出来。但是有没有想过一个问题,如果要排序的对象不是int呢,是其他类型呢。如果是没有办法直接进行大小比较的类型呢?(其实函数重载也是可以撸出来的哈,但是今天不说这个,其实函数重载也挺麻烦的)
答案就是:能识别该类型的代码必须在委托中传递一个封装的方法,这个方法可以比较。e.g.
class BubbleSorter
{
static public void Sort(IList sortArray,Func comparison)
{
bool swapped = true;
do{
swapped = false;
for(int i=0;i
下面就来一个完整的可以运行的代码进行冒泡排序,假设要对员工按照薪水进行排序。e.g.
using System;
using System.Collections;
using System.Collections.Generic;
namespace Demo
{
class Program
{
static void Main()
{
//类实例数组
Employee[] employees =
{
new Employee("Bugs Bunny",2000),
new Employee("Elmer Fudd",1000),
new Employee("Daffy Duck",2500),
new Employee("Wile Coyote",4000),
};
BubbleSorter.Sort(employees,Employee.CompareSalary);
//对排序好的类数组进行遍历
foreach(var employee in employees)
{
Console.WriteLine(employee);
}
}
}
}
class BubbleSorter
{
static public void Sort(IList sortArray,Func comparison)
{
bool swapped = true;
do{
swapped = false;
for(int i=0;i
委托可以包含多个方法吗?当然可以,这样的委托叫做多播委托。如果调用多播委托,就可以按顺序连续调用多个方法。为此,委托的签名就必须返回void,否则,就只能得到委托调用的最后一个方法的结果。
在前面的实例中,因为要存储对两个方法对引用,所以实例化了一个委托数组。而用到多播委托,可以直接在同一个多播委托中添加两个操作,e.g.:
Action operations = MathOperations.MultiplyByTwo;
operations += MathOperations.Square;
所以刚才讲的例子的程序随时应变,应该变成这样:
using System;
class MathOperations
{
public static void MultiplyByTwo(double value)
{
double result = value * 2;
Console.WriteLine("MultiplyByTwo:{0} gives {1}",value,result);
}
public static void Square(double value)
{
double result = value * value;
Console.WriteLine("Square:{0} gives {1}",value,result);
}
}
namespace Demo
{
class Program
{
static void Main()
{
Action operations = MathOperations.MultiplyByTwo;
operations += MathOperations.Square;
ProcessAndDisplayNumber(operations,2.0);
ProcessAndDisplayNumber(operations,7.94);
ProcessAndDisplayNumber(operations,1.414);
Console.WriteLine();
}
static void ProcessAndDisplayNumber(Action action,double value)
{
Console.WriteLine();
Console.WriteLine("processAndDisplayNumber called with values = {0}",value);
action(value);
}
}
}
其实匿名委托是一种更实用的委托方式,按理说应该在三中放的,但是我觉得比较重要,就另起一个部分放在这里了。
先不多说,直接代码:
using System;
namespace Demo
{
class Program
{
static void Main()
{
string mid = ", middle part, ";
Func anonDel = delegate(string param)
{
param += mid;
param += " and this was added to the string.";
return param;
};
Console.WriteLine(anonDel("Start of string"));
}
}
}
解释:Func
其实匿名就是把方法名给省去了。而在C#3.0以后,开始用Lambda表达式替代匿名方法。
所以以上代码可以改为:
using System;
namespace Demo
{
class Program
{
static void Main()
{
string mid = ", middle part, ";
Func lambda = param =>
{
param += mid;
param += " and this was added to the string.";
return param;
};
Console.WriteLine(lambda("Start of string"));
}
}
}
Lambda云算符号“=>”的左边列出了需要的参数,右边赋予了Lambda变量的方法的实现代码。
暂时就先说到这里。恩。