C#委托学习(三):委托链—同时委托多个方法

上一节中介绍了下编译器是如何来翻译委托的,从中间语言的角度去看委托,希望可以帮助大家进一步的理解委托,然而之前的介绍都是委托只是封装一个方法,那委托能不能封装多个方法呢?因为生活中经常会听到,我代表大家的意见等这样的说话,既然委托也是一个代表,那他如果只能代表一个人,那他的魅力就不是很大了吧,所以我们就会委托能不能代表多个方法的? 答案是可以的,这就是本节要讲的内容——委托链,委托链也是一个委托,只是因为它是把多个委托链在一起,所以我们就以委托链来这么称呼它的。 

一、到底什么是委托链
我们平常实例化委托对象时都是绑定一个方法的, 前一个专题介绍的委托也是包装了一个方法的, 用前面的例子就是委派律师的只有一个人,也就是当事人只有一个的,但是现实生活中显然不是这样的,在官司的时候律师可以同时接多个案子,也是接收多个当时人的委派,这样,该律师就与多个当事人绑定在一起了, 需要了解多个当事人的案件情况的。其实这就是生活中的委托链,此时这位律师不仅仅是一个人的代表律师了,而是多个当事人的律师。生活中的委托链和C#中的委托链很类似的,现在就说说C#中的委托链到底是个什么的?

首先 委托链就是一个委托 ,所以大家不要看到委托链感觉又是什么C#中的新特性的,然而要把多个委托链在一起,就必须存储多个委托的引用,那委托链对象是在哪里存储多个委托的引用的呢?还记得我们上一专题中,我们介绍的委托类型有三个非公共字段的吗?这三个字段是——_target,methodPtr 和_invocationList,至于这三个字段具体代表什么大家可以查看我的上一专题的文章,然而_invocationList 字段正是存储多个委托引用的地方的。

为了更好的解释_invocationList是如何来存储委托引用的,下面先看一个委托链的例子和运行结果,然后再分析原因:
using System;

namespace DelegateTest
{
public class Program
{
    // 声明一个委托类型,它的实例引用一个方法
    // 该方法回去一个int 参数,返回void类型
    public delegate void DelegateTest(int parm);

    public static void Main(string[] args)
    {
        // 用静态方法来实例化委托
        DelegateTest dtstatic = new DelegateTest(Program.method1);

        // 用实例方法来实例化委托
        DelegateTest dtinstance = new DelegateTest(new Program().method2);

        // 隐式调用委托
        dtstatic(1);

        // 显式调用Invoke方法来调用委托
        dtinstance.Invoke(1);

        // 隐式调用委托
        dtstatic(2);

        // 显式调用Invoke方法来调用委托
        dtinstance.Invoke(2);
        Console.Read();
    }
    private static void method1(int parm)
    {
        Console.WriteLine("调用的是静态方法,参数值为:" + parm);
    }

    private void method2(int parm)
    {
        Console.WriteLine("调用的是实例方法,参数值为:" + parm);
    }
}
}

运行结果:


下面就来分析下为什么会出现这样的结果的:

一开始我们实例化了两个委托变量,如下代码:
// 用静态方法来实例化委托
DelegateTest dtstatic = new DelegateTest(Program.method1);

// 用实例方法来实例化委托
DelegateTest dtinstance = new DelegateTest(new Program().method2);

委托变量dtstatic和dtinstance引用的委托对象的初始状态如下图:


然后我们定义了一个委托类型的引用变量delegatechain,刚开始它没有任何委托对象,是一个空引用,当我们执行下面的一行代码时:
delegatechain = (DelegateTest)Delegate.Combine(delegatechain, dtstatic);

Combine 方法发现试图合并的是null和dtstatic,在内部,Combine直接返回dtstatic中的对象,此时delegatechain和dtstatic变量引用的都是同一个委托对象,如下图所示:


为了演示委托链,我们通过代码在再添加一个委托,此时就再调用了Combine方法,代码如下:
delegatechain = (DelegateTest)Delegate.Combine(delegatechain, dtinstance);

这时候,Combine方法发现delegatechain已经引用了一个委托对象了(此时已经引用了destatic引用的委托对象了),所以Combine会构造一个新的委托对象(这一点很想String.Concat,我们简单的使用是通过+操作符把两个字符串连接起来,这个新的委托对象会对它的私有字段_target和_methodPtr字段进行初始化,然后此时_invocationList字段初始化为引用了一个委托对象的数组,这个数组的第一个元素(下标为0)就是被初始化为引用包装了method1方法的委托,数组的二个元素被初始化为引用包装了method2方法的委托(也就是dtinstance引用的委托对象),最后delegaechain被设为引用新建的这个委托对象,下面是一个图,可以帮助大家理解委托链(也叫多播委托):


同样的道理,如果是添加第三个委托给委托链,过程也是和上面一样的, 此时又会新建一个委托对象,此时_invocationList字段会初始化为引用一个保存这三个委托对象数组,然而有人会问了——对于已经引用了委托对象的委托类型变量调用Combine方法后会创建一个新的委托对象,然后对新的这个委托对象的三个字段进行重新初始化话,最后把之前的委托类型变量引用新创建的委托对象(这里就帮大家总结下委托链的创建过程),那之前的委托对象怎么办呢? 相信大部分人会有这个疑问的,这点和字符串的Concat方法很像,之前的委托对象和——invocationList字段引用的数组会被垃圾回收掉(正是因为这样,委托和字符串String一样是不可变的)。

注意:我们还可以调用Delegate的Remove方法从链中删除委托,如调用下面代码时:
delegatechain =(DelegateTest)Delegate.Remove(delegatechain,new DelegateTest(method1));Remove

方法被调用时,它会扫描delegateChain(第一个参数)所引用的委托对象内部维护的委托数组( 如果对于委托数组为空的情况下调用Remove方法将不会有任何作用,就是不会删除任何委托引用,这里主要是说明扫描是从委托数组里进行扫描 ),如果找到delegateChain引用的委托对象的_target和_methodPtr字段和第二个参数(新创建的委托)中的字段匹配的委托,如果删除之后数组中只剩下一个数据项时,就返回那个数据项(而不会去新建一个委托对象再初始化的,此时的_invocationList为null,而不是保存一个委托对象引用的数组了,具体可以Remove一个后调试看看的),如果此时数组中还剩余多个数据项,就新建一个委托对象——其中创建并初始化_invocationList数组(此时的数组引用的委托对象已经少了一个了,因为用Remove方法删除了),并且,每次Remove方法调用只能从链中删除一个委托,而不会删除有匹配的_target和_methodPtr字段的所有委托(这个大家可以调试看看的)。 

二、如何对委托链中的委托调用进行控制
通过上面相信大家可以理解如何创建一个委托链对象的,但是从运行结果中还可以看出,每次调用委托链时,委托链包装的每个方法都会顺序被执行,如果委托链中被调用的委托抛出一个异常,这样链中的后续所有对象都不能被调用,并且如果委托的前面具有一个非void的返回类型,则只有最后一个返回值会被保留,其他所有回调方法的返回值都会被舍弃,这就意味着其他所有操作的返回值都永远看不到的吗? 事实却不是这样的,我们可以通过调用Delegate.GetInvocationList方法来显式调用链中的每一个委托,同时可以添加一些自己的定义输出。

GetInvocationList 方法返回一个由Delegate引用构成的数组,其中每一个数组都指向链中的一个委托对象。在内部,GetInvocationList创建并初始化一个数组,让数据的每一个元素都引用链中的一个委托,然后返回对该数组的一个引用。如果_invocatinList字段为null,返回的数组只有一个元素,该元素就是委托实例本身。下面就通过一个程序来演示下的:
namespace DelegateChainDemo
{
class Program
{
    // 声明一个委托类型,它的实例引用一个方法
    // 该方法回去一个int 参数,返回void类型
    public delegate string DelegateTest();

    static void Main(string[] args)
    {
        // 用静态方法来实例化委托
        DelegateTest dtstatic = new DelegateTest(Program.method1);

        // 用实例方法来实例化委托
        DelegateTest dtinstance = new DelegateTest(new Program().method2);
        DelegateTest dtinstance2 = new DelegateTest(new Program().method3);
        // 定义一个委托链对象,一开始初始化为null,就是不代表任何方法(我就是我,我不代表任何人)
        DelegateTest delegatechain = null;
        delegatechain += dtstatic;
        delegatechain += dtinstance;
        delegatechain += dtinstance2;

        delegatechain =(DelegateTest)Delegate.Remove(delegatechain,new DelegateTest(method1));
        delegatechain = (DelegateTest)Delegate.Remove(delegatechain, new DelegateTest(new Program().method2));
        Console.WriteLine(Test(delegatechain));
        Console.Read();
    }

    private static string method1()
    {
        return "这是静态方法1";
    }

    private string method2()
    {
        throw new Exception("抛出了一个异常");
    }

    private string method3()
    {
        return "这是实例方法3";
    }
    // 测试调用委托的方法
    private static string Test(DelegateTest chain)
    {
        if (chain == null)
        {
            return null;
        }

        // 用这个变量来保存输出的字符串
        StringBuilder returnstring = new StringBuilder();

        // 获取一个委托数组,其中每个元素都引用链中的委托
        Delegate[] delegatearray = chain.GetInvocationList();

        // 遍历数组中的每个委托
        foreach (DelegateTest t in delegatearray)
        {
            try
            {
                //调用委托获得返回值
                returnstring.Append(t() + Environment.NewLine);
            }
            catch (Exception e)
            {
                returnstring.AppendFormat("异常从 {0} 方法中抛出, 异常信息为:{1}{2}", t.Method.Name, e.Message, Environment.NewLine);
            }
        }

        // 把结果返回给调用者
        return returnstring.ToString();
    }
}
}

运行结果截图:


从运行结果可以看出,此时我们可以获得每一个回调方法的返回值,并且可以加入一些自定义的返回值的(程序中加入了换行字符串),这样就可以对委托链中的每个委托对象进行控制了,即使其中一个抛出异常,此时我们也可以进行捕获,而不会导致后续的委托对象不能被调用的问题。 

三、总结
本专题主要介绍如何创建一个委托链以及对于创建一个委托链的过程进行了详细的分享,第二部分主要先指出了委托了一些局限性,然后通过调用GetInvocationList方法来返回一个委托数组,这样就可以通过遍历委托数组中的每个委托来通知委托的调用过程,这样就可以对委托链的调用进行更多的控制的。到此本专题也就介绍完了,通过这三个专题对委托的介绍,相信大家会对委托有一个更深的理解。
来源:.net学习网
说明:所有来源为  .net学习网 的文章均为原创,如有转载,请在转载处标注本页地址,谢谢!
  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在C#中,委托和多线程是常用的处理并发编程的方法委托是一种类型,它可以存储对一个或多个方法的引用,并允许在需要时调用这些方法。通过使用委托,我们可以在不同的线程中执行不同的方法。\[2\] 在C#中,线程是使用Thread类处理的。可以通过创建Thread对象并传递一个方法作为参数来创建线程。例如,可以使用Thread类的构造函数创建一个新的线程,并将一个方法作为参数传递给它。然后,可以调用Start方法来启动线程的执行。\[1\] 然而,在使用多线程时,需要注意跨线程访问控件的问题。一般情况下,窗体的控件是属于主线程的,如果在其他线程中直接访问控件,会导致报错。为了解决这个问题,可以使用委托来在其他线程中更新控件的值。首先,需要声明一个委托类型,然后创建一个委托对象,并将要执行的方法作为参数传递给它。接下来,可以创建一个新的线程,并在该线程中调用Invoke方法来更新控件的值。\[2\] 另外,C#还提供了使用Task类来创建多线程的方法。可以使用Task.Factory.StartNew方法来创建一个新的任务,并传递一个方法作为参数。然后,可以使用Task.WaitAll方法等待所有任务执行完成,或者使用Task.WaitAny方法等待任意一个任务执行完成。\[3\] 总结起来,委托和多线程是C#中处理并发编程的常用方法委托可以用于在不同的线程中执行不同的方法,而多线程可以通过Thread类或Task类来创建和管理线程。在使用多线程时,需要注意跨线程访问控件的问题,并可以使用委托来解决这个问题。 #### 引用[.reference_title] - *1* *2* *3* [c#多线程和委托的使用](https://blog.csdn.net/qq_39559182/article/details/103954877)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值