c#的委托

最近一直遇到c#的委托,刚开始看的云里雾里的,后来以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 <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变量的方法的实现代码。

暂时就先说到这里。恩。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

keneyr

老爷~给小的赏点盘缠吧555~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值