C#委托初探

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/keneyr/article/details/87363571
         <!--一个博主专栏付费入口结束-->
        <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
  
  

注意:符号优先级在这里格外重要。比如:


  
  
  1. *kl( int) //意味着kl()是一个返回指针的函数
  2. (*kl)( int) //意味着kl是一个指向函数的指针

在函数地址赋给函数指针成功以后,我们就可以用函数指针调用函数了。

e.g.


  
  
  1. #include <iostream>
  2. using namespace std;
  3. double betsy(int);
  4. double pam(int);
  5. void estimate(int lines,double (*pf)(int));
  6. int main(){
  7. int code;
  8. cout<< "How many lines of code do you need? ";
  9. cin>>code;
  10. cout<< "Here's Betsy's estimate:\n";
  11. estimate(code,betsy);
  12. cout<< "Here's pam's estimate:\n";
  13. estimate(code,pam);
  14. return 0;
  15. }
  16. double betsy(int lns){
  17. return 0.05*lns;
  18. }
  19. double pam(int lns){
  20. return 0.03*lns+ 0.004*lns*lns;
  21. }
  22. void estimate(int lines,double (*pf)(int)){
  23. cout<<lines<< " lines will take ";
  24. cout<<(*pf)(lines)<< " hours"<<endl;
  25. }

二、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.实例委托

定义委托的语法如下:


  
  
  1. delegate void IntMethodInvoker(int x); //定义一个委托IntMethodInvoker,并指定该委托的每个实例都可以包含一个方法的引用,该方法带有一个int参数,并返回void
  2. delegate double TwoLongsOp(long first,long second); //定义一个委托TwoLongsOp,并指定该委托的每个实例都可以包含一个方法的引用,该方法带有两个long参数,并返回double类型
  3. delegate string GetAString(); //定义一个委托GetAString,并指定该委托的每个实例都可以包含一种方法的引用,该方法没有参数,返回为string类型

注意:类有两个不同的术语,“类”表示较广义的定义,“对象”表示类的实例。但是委托只有一个术语。在创建委托的实例时,所创建的委托的实例仍然成为委托。

e.g.   


  
  
  1. using System;
  2. class MathOperations
  3. {
  4. public static double MultiplyByTwo(double value)
  5. {
  6. return value * 2;
  7. }
  8. public static double Square(double value)
  9. {
  10. return value * value;
  11. }
  12. }
  13. namespace Demo
  14. {
  15. //定义委托
  16. delegate double DoubleOp(double x);
  17. class Program
  18. {
  19. static void Main()
  20. {
  21. //实例化一个委托数组,该数组的每个元素都初始化为由MathOperations类实现的不同操作
  22. DoubleOp[] operations =
  23. {
  24. MathOperations.MultiplyByTwo,
  25. MathOperations.Square
  26. };
  27. //遍历委托数组,调用不同的方法
  28. for( int i = 0; i<operations.Length; i++)
  29. {
  30. Console.WriteLine( "Using operations[{0}]:",i);
  31. ProcessAndDisplayNumber(operations[i], 2.0);
  32. ProcessAndDisplayNumber(operations[i], 7.94);
  33. ProcessAndDisplayNumber(operations[i], 1.414);
  34. Console.WriteLine();
  35. }
  36. }
  37. static void ProcessAndDisplayNumber(DoubleOp action,double value)
  38. {
  39. // 实际上是调用action委托实例  封装的方法,其返回结果存储在result中。
  40. double result = action( value);
  41. Console.WriteLine( "Value is {0},result of operation is {1}", value,result);
  42. }
  43. }
  44. }

在上面的那个程序中:

  • operations[i]表示“这个委托”。换言之,就是委托表示的方法
  • operations[i](2.0)表示“实际上调用这个方法,参数放在圆括号中”

所以委托、类、方法之间的情况,一定要理解清楚咯。我的理解就是委托其实和类的使用是差不多的,都需要定义和实例化,但是委托和方法又离的很近很近,它可以直接代表方法,并且作为参数使用(这点又和函数指针很像)。

注意:在这里只是申请了委托数组,并不是多播委托。多播委托将在后面讲述。

三、深入c#委托

随着c#的版本更新,出现了更加实用的委托方式。就是Action<T>Func<T>委托。

Action<T>委托表示引用一个void返回类型的方法,Function<T>委托允许调用带返回类型的方法。

下面以BubbleSorter(冒泡排序)为例,讲解一下委托的优势。

在学习到委托之前,一说到冒泡排序,大脑还没思考,手就开始机械化撸代码了。一般都是这么写的:


  
  
  1. for( int i= 0;i<sortArray.Length -1;i++)
  2. {
  3. if(sortArray[i]>sortArray[i+ 1])
  4. {
  5. int temp = sortArray[i];
  6. sortArray[i]=sortArray[i+ 1];
  7. sortArray[i+ 1]=temp;
  8. }
  9. }

对吧,这是程序员必修课了基本上,闭着眼也能撸出来。但是有没有想过一个问题,如果要排序的对象不是int呢,是其他类型呢。如果是没有办法直接进行大小比较的类型呢?(其实函数重载也是可以撸出来的哈,但是今天不说这个,其实函数重载也挺麻烦的)

答案就是:能识别该类型的代码必须在委托中传递一个封装的方法,这个方法可以比较。e.g.


  
  
  1. class BubbleSorter
  2. {
  3. static public void Sort<T>(IList<T> sortArray,Func<T,T, bool> comparison)
  4. {
  5. bool swapped = true;
  6. do{
  7. swapped = false;
  8. for( int i= 0;i<sortArray.Count -1;i++)
  9. {
  10. if(comparison(sortArray[i+ 1],sortArray[i]))
  11. {
  12. T temp = sortArray[i];
  13. sortArray[i] = sortArray[i+ 1];
  14. sortArray[i+ 1] = temp;
  15. swapped = true;
  16. }
  17. }
  18. } while (swapped);
  19. }
  20. }

下面就来一个完整的可以运行的代码进行冒泡排序,假设要对员工按照薪水进行排序。e.g.


  
  
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. namespace Demo
  5. {
  6. class Program
  7. {
  8. static void Main()
  9. {
  10. //类实例数组
  11. Employee[] employees =
  12. {
  13. new Employee( "Bugs Bunny", 2000),
  14. new Employee( "Elmer Fudd", 1000),
  15. new Employee( "Daffy Duck", 2500),
  16. new Employee( "Wile Coyote", 4000),
  17. };
  18. BubbleSorter.Sort(employees,Employee.CompareSalary);
  19. //对排序好的类数组进行遍历
  20. foreach( var employee in employees)
  21. {
  22. Console.WriteLine(employee);
  23. }
  24. }
  25. }
  26. }
  27. class BubbleSorter
  28. {
  29. static public void Sort<T>(IList<T> sortArray,Func<T,T, bool> comparison)
  30. {
  31. bool swapped = true;
  32. do{
  33. swapped = false;
  34. for( int i= 0;i<sortArray.Count -1;i++)
  35. {
  36. if(comparison(sortArray[i+ 1],sortArray[i]))
  37. {
  38. T temp = sortArray[i];
  39. sortArray[i] = sortArray[i+ 1];
  40. sortArray[i+ 1] = temp;
  41. swapped = true;
  42. }
  43. }
  44. } while (swapped);
  45. }
  46. }
  47. class Employee
  48. {
  49. public Employee(string name,decimal salary)
  50. {
  51. this.Name = name;
  52. this.Salary = salary;
  53. }
  54. public string Name { get; private set;}
  55. public decimal Salary { get; private set;}
  56. public override string ToString()
  57. {
  58. return string.Format( "{0},{1:C}",Name,Salary);
  59. }
  60. public static bool CompareSalary(Employee e1,Employee e2)
  61. {
  62. return e1.Salary<e2.Salary;
  63. }
  64. }

委托可以包含多个方法吗?当然可以,这样的委托叫做多播委托。如果调用多播委托,就可以按顺序连续调用多个方法。为此,委托的签名就必须返回void,否则,就只能得到委托调用的最后一个方法的结果。

在前面的实例中,因为要存储对两个方法对引用,所以实例化了一个委托数组。而用到多播委托,可以直接在同一个多播委托中添加两个操作,e.g.:


  
  
  1. Action< double> operations = MathOperations.MultiplyByTwo;
  2. operations += MathOperations.Square;

所以刚才讲的例子的程序随时应变,应该变成这样:


  
  
  1. using System;
  2. class MathOperations
  3. {
  4. public static void MultiplyByTwo(double value)
  5. {
  6. double result = value * 2;
  7. Console.WriteLine( "MultiplyByTwo:{0} gives {1}",value,result);
  8. }
  9. public static void Square(double value)
  10. {
  11. double result = value * value;
  12. Console.WriteLine( "Square:{0} gives {1}",value,result);
  13. }
  14. }
  15. namespace Demo
  16. {
  17. class Program
  18. {
  19. static void Main()
  20. {
  21. Action< double> operations = MathOperations.MultiplyByTwo;
  22. operations += MathOperations.Square;
  23. ProcessAndDisplayNumber(operations, 2.0);
  24. ProcessAndDisplayNumber(operations, 7.94);
  25. ProcessAndDisplayNumber(operations, 1.414);
  26. Console.WriteLine();
  27. }
  28. static void ProcessAndDisplayNumber(Action<double> action,double value)
  29. {
  30. Console.WriteLine();
  31. Console.WriteLine( "processAndDisplayNumber called with values = {0}",value);
  32. action(value);
  33. }
  34. }
  35. }

四、匿名委托

其实匿名委托是一种更实用的委托方式,按理说应该在三中放的,但是我觉得比较重要,就另起一个部分放在这里了。

先不多说,直接代码:


  
  
  1. using System;
  2. namespace Demo
  3. {
  4. class Program
  5. {
  6. static void Main()
  7. {
  8. string mid = ", middle part, ";
  9. Func< string, string> anonDel = delegate( string param)
  10. {
  11. param += mid;
  12. param += " and this was added to the string.";
  13. return param;
  14. };
  15. Console.WriteLine(anonDel( "Start of string"));
  16. }
  17. }
  18. }

解释:Func<string,string>委托接受一个字符串参数,返回一个字符串。anonDel是这种委托类型的变量。不是把方法名赋予这个变量,而是使用一段简单的代码:它的前面是关键字delegate,后面是一个字符串参数。

其实匿名就是把方法名给省去了。而在C#3.0以后,开始用Lambda表达式替代匿名方法。

所以以上代码可以改为:


  
  
  1. using System;
  2. namespace Demo
  3. {
  4. class Program
  5. {
  6. static void Main()
  7. {
  8. string mid = ", middle part, ";
  9. Func< string, string> lambda = param =>
  10. {
  11. param += mid;
  12. param += " and this was added to the string.";
  13. return param;
  14. };
  15. Console.WriteLine(lambda( "Start of string"));
  16. }
  17. }
  18. }

Lambda云算符号“=>”的左边列出了需要的参数,右边赋予了Lambda变量的方法的实现代码。

暂时就先说到这里。恩。

发布了14 篇原创文章 · 获赞 5 · 访问量 6041
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/keneyr/article/details/87363571
         <!--一个博主专栏付费入口结束-->
        <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>

猜你喜欢

转载自blog.csdn.net/weixin_43277501/article/details/103058308