02011401 委托01-委托基础、调用带引用参数的委托

02011401 委托01-委托基础、调用带引用参数的委托

1 什么是委托

  • 委托(Delegate):是持有一个或多个方法的对象。
    • 一般情况下不会想要“执行”一个对象,但委托与典型的对象不同,可以执行委托,这时委托会执行他所“持有”的方法。
    • 可以将委托视为一种程序特性,它的特点在于委托是用来表示方法的。
    • 为了便于理解,可以将委托视为方法的变量。

2. 委托的引入

  • 在开发中,我们在类A中想调用一个方法F01,但这个方法定义在类B当中。此时,如果我们想要调用F01,首先需要创建B的对象b,然后通过b.F01()的形式调用该方法。

  • 设想,我们想调用F01,是否可以不通过对象b,而是直接调用F01呢?

    • 答案是可以通过委托来实现。

3. 通过对象调用方法与通过委托调用方法对比

// 1. 通过对象调用方法
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DelegateDemo01
{
    class Program
    {
        static void Main(string[] args)
        {
            ClassDemo01 cDemo01 = new ClassDemo01();
            cDemo01.PrintNow();

            Console.ReadLine();
        }
    }

    class ClassDemo01
    {
        public void PrintNow()
        {
            Console.WriteLine("我是通过对象调用方法!");
        }
    }
}

控制台输出:
我是通过对象调用方法!
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// 2. 通过委托对象调用方法
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DelegateDemo01
{
    // @1 声明委托类型(在类的外面)
    delegate void MyDelegate();
    class Program
    {
        // @2 声明方法
        static void PrintNow()
        {
            Console.WriteLine("我是通过委托对象调用方法!");
        }
        static void Main(string[] args)
        {
            // @3 声明委托变量
            MyDelegate myDelegate01;

            // @4 创建一个包含PrintNow的委托对象,并将该对象赋值给变量myDelegate01
            myDelegate01 = new MyDelegate(PrintNow);

            // @5 调用方法
            myDelegate01(); // 调用形式1:使用()调用方法
            myDelegate01.Invoke(); // 调用形式2:使用委托特有的Invoke()方法来调用

            Console.ReadLine();
        }
    }
}

控制台输出:
我是通过委托对象调用方法!
我是通过委托对象调用方法!

说明:
1. 在@1处,声明了一个委托类型MyDelegate,注意是委托类型而不是委托对象。
2. 在@3处,声明了一个局部变量myDelegate01(委托是引用类型,如果不赋值默认为null),他将持有一个MyDelegate类型的委托对象的引用(注意这里并不会创建委托对象)。
3. 在@4处创建了一个委托对象,这个委托对象包含在@2处创建的PrintNow方法。
4. 在@5处调用方法,有两种调用形式。

4. 委托概述

  • 委托和类一样,是一种用户定义类型。
    • 类表示的是数据和方法的集合。
    • 委托则持有一种或多种方法,以及一系列预定义操作。
  • 使用委托的步骤:
    • step01 → 声明一个委托类型,委托声明和方法声明类似,只是没有实现块。
    • step02 → 声明一个委托变量。
    • step03 → 创建一个委托类型的对象(包含某个方法的引用),并将这个对象赋值给委托变量;或者将某个方法的引用直接赋值给委托变量,使得委托变量与这个方法关联起来。注意与委托关联的方法的参数类型和返回类型要和定义的委托类型相同。
    • step04 → 调用委托,可以通过()来调用,也可以用Invoke()方法来调用。

5. 声明委托类型

// 声明委托的基本格式
delegte void MyDelegate(int x);

说明:
1. 声明委托以关键字delegate开头,没有用{}包含的方法主体。
2. 声明委托与声明方法很相似,有返回类型和参数类型,返回类型和参数类型指定了委托可以包含(或关联)的方法的形式。
2. 委托声明是类型声明,不能再类的内部声明。

6. 创建委托对象

  • 委托是引用类型,因此有引用(栈里面)和对象(堆里面)。在委托类型声明之后,我们可以声明变量并创建委托对象。
6.1 创建委托变量
MyDelete mydelegate01;
6.2 创建委托对象
  • 有两种创建形式
// 形式1:使用带new运算符的对象创建表达式。
myDelegate01 = new MyDelegate(PrintNow);

说明:new运算符的操作数组成之一为委托类型名(即MyDelegate);操作数之二为一组圆括号(),其中包含作为调用列表中第一个成员方法(可以是实例方法,也可以是静态方法)的名称。
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// 形式2:使用快捷语法创建委托对象。
myDelegate01 = PrintNow;

说明:形式1和形式2完全等价。
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// 使用快捷语法改写本章3小节中2段代码。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DelegateDemo01
{
    delegate void MyDelegate();
    class Program
    {
        static void PrintNow()
        {
            Console.WriteLine("通过快捷语法使用委托!");
        }
        static void Main(string[] args)
        {
            MyDelegate myDelegate01;

            // 使用快捷语法创建委托对象。
            myDelegate01 = PrintNow;

            myDelegate01();
            myDelegate01.Invoke();

            Console.ReadLine();
        }
    }
}

控制台输出:
通过快捷语法使用委托!
通过快捷语法使用委托!
  • 创建静态方法或实例方法的委托对象
delVar  = new MyDel(myInstObj.MyM1); // 通过实例方法创建委托对象
dVar = new MyDel(SClass.Other2); // 通过静态方法创建委托对象

// 上述代码段可以简写为如下形式
delVar  = myInstObj.MyM1;
dVar = new SClass.Other2;

7. 给委托赋值

myDelegate01 = PrintNow01; // 创建委托对象并赋值给委托变量。
myDelegate01 = PrintNow02; // 创建新的委托对象并赋值给委托变量,此时包含PrintNow01的委托对象会被垃圾回收器回收。

8. 委托是一个方法列表

  • 委托对象本质上是一个包含有序方法列表的对象(或者说委托是一个方法列表),方法列表里面方法的返回值和参数类型与声明委托类型时保持一致。
  • 本章到目前为止,所有的委托在调用列表中只有一个方法。委托可以使用额外的运算符来组合,这个运算符会创建一个新的委托,其调用列表连接了操作数的两个委托的调用列表副本(注意理解副本)。
MyDelegate myDelegate01 = PrintNow01; // 创建了一个委托对象,该对象的方法列表中首位为PrintNow01,并将这个对象赋值给委托变量myDelegate01。
MyDelegate myDelegate02 = PrintNow02; // 创建了一个委托对象,该对象的方法列表中首位为PrintNow02,并将这个对象赋值给委托变量myDelegate02。
MyDelegate myDelegate03 = myDelegate01 + myDelegate02; // 新创建了一个委托对象,该对象的方法列表中首位为PrintNow01,第二位为PrintNow02,并将这个对象赋值给myDelegate03。

说明:委托是恒定的,即委托对象一旦被创建就不能再改变。

9. 组合委托

  • 迄今为止,我们见过的所有委托在调用列表中只有一个方法。委托可以使用额外的运算符来组合。
    • 这个运算符会创建一个新的委托,其调用列表连接了作为惨煮熟的两个委托的调用列表副本。
MyDel delA = myInstObj.MyM1;
MyDel delB = SClass.OtherM2;

MyDel delC = delA + delB; // 组合调用列表
图片链接丢失
  • 尽管属于组合委托让我们觉得好笑操作数委托被修改了,但其实它们并没有被修改。事实上,委托是恒定的。委托对象被创建后不能再被改变。

10. 为委托添加或删除方法

  • 使用+=或者-=可以给委托对象的调用列表中增加或者删除方法。由于委托对象一旦创建,它就恒定不变,因此使用+=或-=运算符时,实际发生的是创建了一个新的委托。
MyDelegate myDelegate01 = PrintNow01; // 创建委托对象,该对象的方法列表中首位为PrintNow01,并将这个对象赋值给委托变量myDelegate01。
myDelegate01 += PrintNow02; // 创建委托对象,该对象方法列表首位为PrintNow01,第二位为PrintNow02,并将新创建的对象赋值给委托变量myDelegate01。
myDelegate01 -= PrintNow02; // 创建委托对象,该对象方法列表首位为PrintNow01,并将新创建的对象赋值给委托变量myDelegate01。
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DelegateDemo01
{
    delegate void MyDelegate();
    class Program
    {
        static void PrintNow01()
        {
            Console.WriteLine("我是第1个打印方法。");
        }
        static void PrintNow02()
        {
            Console.WriteLine("我是第2个打印方法。");
        }
        static void Main(string[] args)
        {
            Console.WriteLine("***********给委托添加方法***********");
            MyDelegate myDelegate01 = PrintNow01;
            myDelegate01 += PrintNow02;
            myDelegate01();

            Console.WriteLine("***********给委托移除方法***********");
            myDelegate01 -= PrintNow02;
            myDelegate01();

            Console.ReadLine();
        }
    }
}

控制台输出:
***********给委托添加方法***********
我是第1个打印方法。
我是第2个打印方法。
***********给委托移除方法***********
我是第1个打印方法。

说明:
1. 使用+=或者-=运算符,都会创建新的委托对象,然后赋值给委托变量。
2. 如果在调用列表中方法有多个实例,-=运算符将从调用列表最后往前开始检索,并且移除第一个与方法匹配的实例。
3. 试图删除委托的调用列表中不存在的方法无效。
4. 如果委托的调用列表被清空(即空委托),我们调用空委托会抛出异常。此时可以将委托与null进行比较来判断委托的调用列表是否为空。如果调用列表为空,则委托为null。

11. 调用委托

  • 可以通过两种方式来调用委托,一种是像方法一样通过()来调用委托,另一种是使用委托的Invoke方法来调用委托。

  • 调用委托时,为了避免调用空委托,我们可以使用if判断是否是空委托,以免抛出异常。

if(null != null)
    myDelegate01();
else
    Console.WriteLine("请注意是空委托!");
  • 调用带返回值的委托
    • 调用列表中最后一个方法的返回值就是委托调用返回的值。
    • 调用列表中所有其它方法的返回值都会被忽略。

12. 调用带引用参数的委托

  • 如果委托有引用参数,参数值会根据调用列表中的一个或多个方法的返回值而改变。
  • 在调用委托列表中下一个方法时,参数的新值(不是初始值)会传给下一个方法。
using System; 

namespace Demo01
{
    delegate void MyDel(ref int x);

    class MyClass
    {
        public void Add2(ref int x)
        {
            x += 2;
        }

        public void Add3(ref int x)
        {
            x += 3;
        }

        static void Main()
        {
            MyClass mc = new MyClass();
            MyDel mDel = mc.Add2;
            mDel += mc.Add3;
            mDel += mc.Add2;

            int x = 5;
            mDel(ref x);
            Console.WriteLine($"Value:{x}");

            Console.ReadLine();
        }
    }
}

控制台输出:
Value:12

结尾

书籍:C#图解教程

著:【美】丹尼尔 · 索利斯;卡尔 · 施罗坦博尔

译:窦衍森;姚琪琳

ISBN:978-7-115-51918-4

版次:第5版

发行:人民邮电出版社

※敬请购买正版书籍,侵删请联系85863947@qq.com※

※本文章为看书或查阅资料而总结的笔记,仅供参考,如有错误请留言指正,谢谢!※

posted @ 2025-08-14 08:43  qinway  阅读(7)  评论(0)    收藏  举报