C#中的委托详解

目录
  • 1.声明委托
  • 2.使用委托
  • 3.使用委托数组
  • 4.Action<T>和Func<T>委托
  • 5.多播委托
  • 6.匿名方法

如果要给方法传递一个方法参数时,就可以使用委托。要传递方法,就必须把方法的细节封装在一钟新类型的对象中,即委托。委托是一种特殊类型的对象,其特殊之处在于,我们以前定义的所有对象都包含数据,而委托只包含一个或多个方法的地址。
.NET版本中,委托指向方法的地址。在C++中,函数指针是一个指向内存位置的指针,但它不是类型安全的。开发者无法判断这个指针实际指向什么,像参数和返回值等项就更不知道了。
.NET委托是类型安全的类,它定义了返回类型和参数的类型。委托类不仅包含对方法的引用,也可以包含对多个方法的引用。

1.声明委托

使用委托和使用类一样,也需要经过定义和实例化两个步骤。首先必须定义要使用的委托,对于委托,定义它就是告诉编译器这种类型的委托表示哪种类型的方法。然后,必须创建该委托的一个或多个实例才能使用。编译器在后台将创建表示该委托的一个类。
定义委托的语法:

delegate void IntMethod(int x);
//定义了一个委托IntMethod,指定该委托的每个实例都可以包含一个或多个方法的引用,引用的方法必须带有一个int参数,并返回void.

因为定义委托基本上是定义一个新类,所以可以在定义类的任何地方定义委托。也可以在委托的定义上使用修饰符:public,private,protected等。
委托派生自基类System.MulticastDelegate,MulticastDelegate又派生自基类System.Delegate.
类有两个不同的术语:“类”表示广义的定义,“对象”表示;类的实例。但委托只有一个术语。在创建委托的实例时,所创建的实例仍称为委托。必须从上下文中确定委托的具体含义。

2.使用委托

定义好委托之后,就可以创建它的一个实例,从而用它存储特定方法的细节。

delegate void IntMethod(int x);

        static void Fun(int x)
        {
            Console.WriteLine(x);
        }

        static void Main()
        {
          int x = 40;
          IntMethod intMethod = new IntMethod(Fun);
          intMethod(x);
          Console.ReadKey();

        }

委托在语法上总是接受一个参数的构造函数,这个参数就是委托引用的方法。这个方法必须匹配最初定义委托时的签名。
使用委托实例的名称,后面加上圆括号,如果需要参数就必须在圆括号内加上参数。
给委托实例提供圆括号和调用委托类的Invoke()方法完全相同:

  intMethod(x);
  intMethod.Invoke(x);

为了减少输入量,只需要给委托实例传递方法地址的名称就可以,这称为委托推断。

IntMethod intMethod = new IntMethod(Fun);
IntMethod intMethod =Fun;

委托推断可以在需要委托实例的任何地方使用。委托推断也可以用于事件,因为事件基于委托。(事件后面文章有介绍)
注意,使用委托可以调用任何类型对象的方法,不管是静态方法还是实例方法。

3.使用委托数组

//先在一个类中定义两个方法:
          class MathOperations
          {
            public static double MultiplyByTwo(double value)
            {
              return value * 2;
            }

            public static double Square(double value)
            {
              return value * value;
            }
          }
//定义一个返回double类型且带有double类型参数的委托
          delegate double DoubleOp(double x);

          class Program
          {
            static void Main()
            {
              //实例化委托数组,和实例化类的数组一样
              DoubleOp[] operations =
              {
                MathOperations.MultiplyByTwo,
                MathOperations.Square
              };

              //遍历数组,使用数组中的每个委托实例
              for (int i = 0; i < operations.Length; i++)
              {
                Console.WriteLine("Using operations[{0}]:", i);
                //将委托实例作为参数传递给ProcessAndDisplayNumber方法
                ProcessAndDisplayNumber(operations[i], 2.0);
                ProcessAndDisplayNumber(operations[i], 7.94);
                ProcessAndDisplayNumber(operations[i], 1.414);
                Console.WriteLine();
              }
            }

            static void ProcessAndDisplayNumber(DoubleOp action, double value)
            {
              //在ProcessAndDisplayNumber中调用委托,执行委托实例引用的方法
              double result = action(value);
              Console.WriteLine(
                 "Value is {0}, result of operation is {1}", value, result);
            }
          }

4.Action<T>和Func<T>委托

除了为每个参数和返回类型定义一个委托类型之外,还可以使用Action<T>和Func<T>委托。
泛型Action<T>委托表示引用一个void返回类型的方法。这个委托类存在不同的变体,可以传递最多16种不同的参数类型。没有泛型参数的Action类调用没有参数的方法。Action<in T>调用带一个参数的方法,Action<in T1,in T2>调用带两个参数的方法,依次类推。
Func<T>委托允许调用带返回类型的方法。与Action<T>类似,Func<T>也存在不同的变体,可以传递最多16种不同的参数类型和一个返回类型。Func<out TResult>委托类型可以调用无参数且带返回类型的方法。
下面使用Func<T>委托实现一个不使用委托很难编写的一个功能:给对象数组排序,如果对象是int或string这样值类型的对象会容易排序,如果是要排序很多自定义的类型的对象,需要编写大量代码。使用委托会减少代码量。
定义包含比较方法的类:
BubbleSorter类实现了一个泛型方法 Sort<T>,第一个参数是要排序的对象数组,第二个是一个委托,传递比较两个对象的方法。这样可以给Sort<T>方法,传递自定义的比较方法。

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;
            }
          }

客户端代码:

Employee[] employees =
          {
            new Employee("Bugs Bunny", 20000),
            new Employee("Elmer Fudd", 10000),
            new Employee("Daffy Duck", 25000),
            new Employee("Wile Coyote", 1000000.38m),
            new Employee("Foghorn Leghorn", 23000),
            new Employee("RoadRunner", 50000)
          };
            //Sort执行了自定义的Employee.CompareSalary方法
          BubbleSorter.Sort(employees, Employee.CompareSalary);

          foreach (var employee in employees)
          {
            Console.WriteLine(employee);
          }

5.多播委托

前面介绍的每个委托只包含一个方法的调用,委托也可以包含多个方法。这种委托称为多播委托。
如果调用多播委托,就可以按顺序调用多个方法,但如果委托的签名不是返回void,就只能得到委托调用的最后一个方法的结果。
使用+=添加方法,-=删除方法。

static void Main()
        {
          Action<double> operations = MathOperations.MultiplyByTwo;
          operations += MathOperations.Square;

          ProcessAndDisplayNumber(operations, 2.0);
          ProcessAndDisplayNumber(operations, 7.0);
          Console.WriteLine();
        }

        static void ProcessAndDisplayNumber(Action<double> action, double value)
        {
          Console.WriteLine();
          Console.WriteLine("ProcessAndDisplayNumber called with value = {0}", value);
          action(value);

        }

        class MathOperations
          {
            public static void MultiplyByTwo(double value)
            {
              double result = value * 2;
              Console.WriteLine("Multiplying by 2: {0} gives {1}", value, result);
            }

            public static void Square(double value)
            {
              double result = value * value;
              Console.WriteLine("Squaring: {0} gives {1}", value, result);
            }
          }

每次调用ProcessAndDisplayNumber方法,都会按顺序调用action委托实例中的两个方法。
输出:

  ProcessAndDisplayNumber called with value = 2
  Multiplying by 2: 2 gives 4
  Squaring: 2 gives 4

  ProcessAndDisplayNumber called with value = 7
  Multiplying by 2: 7 gives 14
  Squaring: 7 gives 49

委托还可以使用+,-运算符:

  Action<double> operations1 = MathOperations.MultiplyByTwo;
  Action<double> operations2 = MathOperations.Square;
  Action<double> operations = operations1 + operations2;
  operations = operations - operations2;

多播委托包含一个逐个调用的委托集合。如果其中一个方法抛出异常,整个迭代就会停止。

  static void One()
  {
    Console.WriteLine("One");
    throw new Exception("Error in one");
  }

  static void Two()
  {
    Console.WriteLine("Two");
  }

  static void Main()
  {
    Action d1 = One;
    d1 += Two;

    try
    {
      d1();
    }
    catch (Exception)
    {
      Console.WriteLine("Exception caught");
    }
  }

委托只调用了第一个方法。因为第一个方法抛出异常,委托的迭代停止,不再调用Two()方法。
避免这个问题,可以使用Delegate类定义的GetInvocationList()方法,它返回一个Delegate对象数组:

  Action d1 = One;
  d1 += Two;

  Delegate[] delegates = d1.GetInvocationList();
  foreach (Action d in delegates)
  {
    try
    {
      d();
    }
    catch (Exception)
    {
      Console.WriteLine("Exception caught");
    }
  }

输出:

  One
  Exception caught
  Two

使用GetInvocationList()方法可以为委托的每个方法传递不同的参数,获取每个方法的返回值。

  static int One(int x)
  {
    return x;
  }

  static int Two(int x)
  {
    return x;
  }

  static void Main()
  {

    Func<int,int> d1 = One;
    d1 += Two;

    Delegate[] delegates = d1.GetInvocationList();
    Func<int, int> d2 = (Func<int, int>)delegates[0];
    Console.WriteLine( d2(1));

    Func<int, int> d3 = (Func<int, int>)delegates[1];
    Console.WriteLine(d3(2));

    Console.ReadKey();
  }

输出:

  1
  2

6.匿名方法

使用匿名方法可以将方法体直接赋给委托实例,而不需要定义一个方法。

  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"));

  }

上面代码不是把方法名赋给委托变量anonDel,而是一段代码,它前面是关键字delegate和参数列表。在使用匿名方法时,可以使用外部变量。
匿名方法的优点是减少了代码量。使用匿名方法,代码执行速度并没有加快。编译器仍定义了一个方法,该方法只有一个自动指定的名称。
使用匿名方法,必须遵守两条规则:

  • (1).在匿名方法中不能使用跳转语句(break,goto,continue)调到匿名方法的外部,外部的代码也不能调到匿名方法内部。
  • (2).匿名方法内部不能访问不安全的代码。也不能在匿名方法使用ref和out

如果需要匿名方法多次编写同一个功能时,就不要用匿名方法了。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • C#委托用法详解

    目录 1.什么是委托 2.委托的定义 3.委托的实例化 3.1使用new关键字 3.2使用匿名方法 3.3使用Lambda表达式 4.泛型委托 5.C#内置泛型委托 6.多播委托 1.什么是委托 从数据结构来讲,委托是和类一样是一种用户自定义类型. 委托是方法的抽象,它存储的就是一系列具有相同签名和返回回类型的方法的地址.调用委托的时候,委托包含的所有方法将被执行. 2.委托的定义 委托是类型,就好像类是类型一样.与类一样,委托类型必须在被用来创建变量以及类型对象之前声明. 委托的声明原型是de

  • C#中委托的基础入门与实现方法

    目录 前言 关于委托 委托的实现 一.基本实现方式 二.使用委托时的一些特殊方式 1.委托实例对象的创建多元化: 2.事件绑定的多种方式 三.委托的几种特殊实现方式 1,使用Action方法 2,使用Func方法 四.委托的一些特殊小知识 1.委托闭包的产生 2,关于事件 总结 前言 似乎委托对于C#而言是一种高级属性,但是我依旧希望你就算第一次看我的文章,也能有很大的收获. 所以本博客的语言描述尽量简单易懂,知识点也是面向初入门对于委托不了解的学习者的.当然如果有幸有大佬发现文章的错误点,也欢

  • c#委托与事件(详解)

    目录 前言 一.声明方法 二.声明委托 三.实例化委托 四.使用委托 总结 前言 .NET中的委托是一个类,它定义了方法的类型,是一个方法容器.委托把方法当作参数,可以避免在程序中大量使用条件判断语句的情况. 项目名为Test,class类名为Program 一.声明方法 在class类中声明几个方法,以便委托的使用. using System; namespace Test { class Program { /// <summary> /// 返回两个数中较大者 /// </summ

  • c# 委托的常见用法

    此篇文章是我一个小白对委托的理解和总结,请高手多多评判指教. 委托就是一种后期绑定机制,说的直白点就是在调用的时候才去传递业务逻辑的一种算法. 委托的创建语法: public delegate int Comparison<in T>(T left, T right);//官方给出的定义泛型委托的demo 语法看似像声明一个变量或方法的签名,但实现上是在声明一个类型.编译器会生成一个派生自System.MulticastDelegate的类(而System.MulticastDelegate派

  • C#内置泛型委托之Action委托

    1.什么是Action泛型委托 Action<T>是.NET Framework内置的泛型委托,可以使用Action<T>委托以参数形式传递方法,而不用显示声明自定义的委托.封装的方法必须与此委托定义的方法签名相对应.也就是说,封装的方法必须具有一个通过值传递给它的参数,并且不能有返回值. 2.Action委托定义 查看Action的定义: using System.Runtime.CompilerServices; namespace System { // // 摘要: //

  • 大白话讲解C# 中的委托

    有一天,你写了好多好多带"形参"的构造函数(就是"方法",同义),而且需要向这些构造函数里传递同样的"实参",然后你就憨憨地一个一个函数的调用并赋予同样的"实参",这一天就这么过去了... 又过了几天,你又要再一次调用这么多函数了,你再老老实实地一个一个函数进行调用?!聪明的你,肯定会想:真TM麻烦!有没有一劳永逸的办法呢? 你需要的正是----"委托" 来,先看一个简单的例子,要表述的是----有一位贵宾

  • c# 委托的本质是什么

    引言 上一个专题已经和大家分享了我理解的--C#中为什么需要委托,专题中简单介绍了下委托是什么以及委托简单的应用的,在这个专题中将对委托做进一步的介绍的,本专题主要对委本质和委托链进行讨论. 一.委托的本质 平时我们很容易使用委托--用C# delegate关键字定义委托,再用new操作符构造委托实例,然后通过调用委托实例来调用回调方法(就是用一个了委托对象的变量来代替方法名,这句话如果刚接触的人不好理解的话,这里给个例子:MyDelegate mydelegate =new Mydelegat

  • C#内置泛型委托之Func委托

    一.什么是Func委托 Func委托代表有返回类型的委托 二.Func委托定义 查看Func的定义: using System.Runtime.CompilerServices; namespace System { // // 摘要: // 封装一个方法,该方法具有两个参数,并返回由 TResult 参数指定的类型的值. // // 参数: // arg1: // 此委托封装的方法的第一个参数. // // arg2: // 此委托封装的方法的第二个参数. // // 类型参数: // T1:

  • C#中的委托详解

    目录 1.声明委托 2.使用委托 3.使用委托数组 4.Action<T>和Func<T>委托 5.多播委托 6.匿名方法 如果要给方法传递一个方法参数时,就可以使用委托.要传递方法,就必须把方法的细节封装在一钟新类型的对象中,即委托.委托是一种特殊类型的对象,其特殊之处在于,我们以前定义的所有对象都包含数据,而委托只包含一个或多个方法的地址..NET版本中,委托指向方法的地址.在C++中,函数指针是一个指向内存位置的指针,但它不是类型安全的.开发者无法判断这个指针实际指向什么,像

  • ASP.NET Core中HttpContext详解与使用

    “传导体” HttpContext 要理解 HttpContext 是干嘛的,首先,看图 图一 内网访问程序 图二 反向代理访问程序 ASP.NET Core 程序中,Kestrel 是一个基于 libuv 的跨平台ASP.NET Core web服务器.不清楚 Kerstrel 没关系,以后慢慢了解. 我们可以理解成,外部访问我们的程序,通过 Http 或者 Https 访问,例如https://localhost:44337/Home/Index,需要通过一个网址,来寻向访问特定的页面. 访

  • Java中双向链表详解及实例

    Java中双向链表详解及实例 写在前面: 双向链表是一种对称结构,它克服了单链表上指针单向性的缺点,其中每一个节点即可向前引用,也可向后引用,这样可以更方便的插入.删除数据元素. 由于双向链表需要同时维护两个方向的指针,因此添加节点.删除节点时指针维护成本更大:但双向链表具有两个方向的指针,因此可以向两个方向搜索节点,因此双向链表在搜索节点.删除指定索引处节点时具有较好的性能. Java语言实现双向链表: package com.ietree.basic.datastructure.dublin

  • IOS 中UIApplication详解及实例

    IOS 中UIApplication详解及实例 以前刚学iPhone开发时,觉得UIApplication这个东西特NB,特神秘,比如它居然能打开一个URL,而且还是用一个很神秘的方法得到实例: [UIApplication sharedApplication] 它对我的神秘感一直保持到今天下午.今天下午负责UI设计的同事在设计,我没有素材,比较清闲,于是发个狠,专门看了一下UIApplication这个类.果然是难者不会,会者不难,看完之后,这个类的神秘感一下子没了.下面让我来揭开它的神秘面纱

  • IOS Object-C 中Runtime详解及实例代码

    IOS Object-C 中Runtime详解 最近了解了一下OC的Runtime,真的是OC中很强大的一个机制,看起来比较底层,但其实可以有很多活用的方式. 什么是Runtime 我们虽然是用Objective-C写的代码,其实在运行过程中都会被转化成C代码去执行.比如说OC的方法调用都会转成C函数 id objc_msgSend ( id self, SEL op, - ); 而OC中的对象其实在Runtime中都会用结构体来表示,这个结构体中包含了类名.成员变量列表.方法列表.协议列表.缓

  • Android中SharedPreference详解及简单实例

     Android中SharedPreference详解 SharedPreference是Android提供的一种轻量级的数据存储方式,主要用来存储一些简单的配置信息,例如,默认欢迎语,登录用户名和密码等.其以键值对的方式存储,使得我们能很方便进行读取和存入. SharedPreference 文件保存在/data/data/<package name>/shared_prefs 路径下(如/data/data/com.android.alarmclock/shared_prefs/com.a

  • Android 中Seekbar详解及简单实例

    Android 中Seekbar详解及简单实例 做到音频播放和音乐播放时,大多数都要用到Seekbar.现在我先简单介绍下Seekbar的几个重要属性. android:max 设置值的大小 . android:thumb="@drawable/" 显示的那个可拖动图标,如果没有设置该参数则为系统默认,如果自己需要重新定义,则将自己需要的图标存放在资源目录 /res/drawable下,然后调用即可. android:thumbOffset 拖动图标的偏量值,可以让拖动图标超过bar的

  • Java中自定义异常详解及实例代码

    Java中自定义异常详解及实例代码 下面做了归纳总结,欢迎批评指正 自定义异常 class ChushulingException extends Exception { public ChushulingException(String msg) { super(msg); } } class ChushufuException extends Exception { public ChushufuException(String msg) { super(msg); } } 自定义异常 En

  • Android Studio打包.so库到apk中实例详解

    Android Studio打包.so库到apk中实例详解 由于在原来的ADT的Eclipse环境中,用ndk_build工具生成了相应的各个.so库文件之后,eclipse工具就会自动把这些库导入到apk中.而Android Studio目前为止(1.1.0版本)还无法做到那么自动,但是我们可以通过以下方式进行. 首先在Android Studio工程的app目录下创建整个jni目录,jni目录里写Android.mk.Application.mk以及各类C/C++和汇编源文件.然后跟原来一样

  • Linux中多线程详解及简单实例

    Linux中多线程详解及简单实例 1.概念 进程:运行中的程序. 线程:一个程序中的多个执行路径.更准确的定义是:线程是一个进程内部的一个控制序列. 2.为什么要有线程? 用fork调用进程代价太高,需要让一个进程同时做多件事情,线程就非常有用. 3.线程的优点和缺点. 优点: (1)有时,让程序看起来是在同时做两件事是非常有用的. 比如在编辑文档时,还能统计文档里的单词个数. (2)一个混杂着输入.计算.输出的程序,利用线程可以将这3个部 分分成3个线程来执行,从而改变程序执行的性能. (3)

随机推荐