<!--一个博主专栏付费入口结束-->
<link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-d284373521.css">
<link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-d284373521.css">
<div class="htmledit_views" id="content_views">
<p>最近一直遇到c#的委托,刚开始看的云里雾里的,后来以c++的函数指针来类比就醍醐灌顶了。所以特此记载下来。</p>
因为最先接触的是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 <iostream>
-
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<<lines<<
" lines will take ";
-
cout<<(*pf)(lines)<<
" hours"<<endl;
-
}
二、c#的委托
参考博客如下:
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<operations.Length; i++)
-
{
-
Console.WriteLine(
"Using operations[{0}]:",i);
-
ProcessAndDisplayNumber(operations[i],
2.0);
-
ProcessAndDisplayNumber(operations[i],
7.94);
-
ProcessAndDisplayNumber(operations[i],
1.414);
-
Console.WriteLine();
-
-
}
-
}
-
static void ProcessAndDisplayNumber(DoubleOp action,double value)
-
{
-
// 实际上是调用action委托实例 封装的方法,其返回结果存储在result中。
-
double result = action(
value);
-
Console.WriteLine(
"Value is {0},result of operation is {1}",
value,result);
-
}
-
}
-
}
在上面的那个程序中:
- operations[i]表示“这个委托”。换言之,就是委托表示的方法
- operations[i](2.0)表示“实际上调用这个方法,参数放在圆括号中”
所以委托、类、方法之间的情况,一定要理解清楚咯。我的理解就是委托其实和类的使用是差不多的,都需要定义和实例化,但是委托和方法又离的很近很近,它可以直接代表方法,并且作为参数使用(这点又和函数指针很像)。
注意:在这里只是申请了委托数组,并不是多播委托。多播委托将在后面讲述。
三、深入c#委托
随着c#的版本更新,出现了更加实用的委托方式。就是Action<T>和Func<T>委托。
Action<T>委托表示引用一个void返回类型的方法,Function<T>委托允许调用带返回类型的方法。
下面以BubbleSorter(冒泡排序)为例,讲解一下委托的优势。
在学习到委托之前,一说到冒泡排序,大脑还没思考,手就开始机械化撸代码了。一般都是这么写的:
-
for(
int i=
0;i<sortArray.Length
-1;i++)
-
{
-
if(sortArray[i]>sortArray[i+
1])
-
{
-
int temp = sortArray[i];
-
sortArray[i]=sortArray[i+
1];
-
sortArray[i+
1]=temp;
-
-
}
-
}
对吧,这是程序员必修课了基本上,闭着眼也能撸出来。但是有没有想过一个问题,如果要排序的对象不是int呢,是其他类型呢。如果是没有办法直接进行大小比较的类型呢?(其实函数重载也是可以撸出来的哈,但是今天不说这个,其实函数重载也挺麻烦的)
答案就是:能识别该类型的代码必须在委托中传递一个封装的方法,这个方法可以比较。e.g.
-
class
BubbleSorter
-
{
-
static
public
void Sort<T>(IList<T> sortArray,Func<T,T,
bool> comparison)
-
{
-
bool swapped =
true;
-
do{
-
swapped =
false;
-
for(
int i=
0;i<sortArray.Count
-1;i++)
-
{
-
if(comparison(sortArray[i+
1],sortArray[i]))
-
{
-
T temp = sortArray[i];
-
sortArray[i] = sortArray[i+
1];
-
sortArray[i+
1] = temp;
-
swapped =
true;
-
}
-
}
-
}
while (swapped);
-
}
-
}
下面就来一个完整的可以运行的代码进行冒泡排序,假设要对员工按照薪水进行排序。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<T>(IList<T> sortArray,Func<T,T,
bool> comparison)
-
{
-
bool swapped =
true;
-
do{
-
swapped =
false;
-
for(
int i=
0;i<sortArray.Count
-1;i++)
-
{
-
if(comparison(sortArray[i+
1],sortArray[i]))
-
{
-
T temp = sortArray[i];
-
sortArray[i] = sortArray[i+
1];
-
sortArray[i+
1] = temp;
-
swapped =
true;
-
}
-
}
-
}
while (swapped);
-
}
-
}
-
-
class
Employee
-
{
-
public Employee(string name,decimal salary)
-
{
-
this.Name = name;
-
this.Salary = salary;
-
}
-
public
string Name {
get;
private
set;}
-
public
decimal Salary {
get;
private
set;}
-
-
-
public override string ToString()
-
{
-
return
string.Format(
"{0},{1:C}",Name,Salary);
-
}
-
-
public static bool CompareSalary(Employee e1,Employee e2)
-
{
-
return e1.Salary<e2.Salary;
-
}
-
-
}
委托可以包含多个方法吗?当然可以,这样的委托叫做多播委托。如果调用多播委托,就可以按顺序连续调用多个方法。为此,委托的签名就必须返回void,否则,就只能得到委托调用的最后一个方法的结果。
在前面的实例中,因为要存储对两个方法对引用,所以实例化了一个委托数组。而用到多播委托,可以直接在同一个多播委托中添加两个操作,e.g.:
-
Action<
double> 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<
double> operations = MathOperations.MultiplyByTwo;
-
operations += MathOperations.Square;
-
-
ProcessAndDisplayNumber(operations,
2.0);
-
ProcessAndDisplayNumber(operations,
7.94);
-
ProcessAndDisplayNumber(operations,
1.414);
-
Console.WriteLine();
-
-
-
}
-
static void ProcessAndDisplayNumber(Action<double> 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<
string,
string> anonDel =
delegate(
string param)
-
{
-
param += mid;
-
param +=
" and this was added to the string.";
-
return param;
-
};
-
Console.WriteLine(anonDel(
"Start of string"));
-
}
-
}
-
}
解释:Func<string,string>委托接受一个字符串参数,返回一个字符串。anonDel是这种委托类型的变量。不是把方法名赋予这个变量,而是使用一段简单的代码:它的前面是关键字delegate,后面是一个字符串参数。
其实匿名就是把方法名给省去了。而在C#3.0以后,开始用Lambda表达式替代匿名方法。
所以以上代码可以改为:
-
using System;
-
-
namespace
Demo
-
{
-
class
Program
-
{
-
static void Main()
-
{
-
string mid =
", middle part, ";
-
-
Func<
string,
string> lambda = param =>
-
{
-
param += mid;
-
param +=
" and this was added to the string.";
-
return param;
-
};
-
Console.WriteLine(lambda(
"Start of string"));
-
}
-
}
-
}
Lambda云算符号“=>”的左边列出了需要的参数,右边赋予了Lambda变量的方法的实现代码。
暂时就先说到这里。恩。
<!--一个博主专栏付费入口结束-->
<link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-d284373521.css">
<link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-d284373521.css">
<div class="htmledit_views" id="content_views">
<p>最近一直遇到c#的委托,刚开始看的云里雾里的,后来以c++的函数指针来类比就醍醐灌顶了。所以特此记载下来。</p>