`

委 托

    博客分类:
  • CLR
 
阅读更多

回调函数是一种非常有用的编程机制,它的存在已经有很多年了。.NET通过委托delegate来提供了一种回调函数机制。委托还允许顺序调用多个方法,并支持调用静态方法和实例方法。

我们先来看看如何使用委托:

 

 delegate void FeedBack(int value);

    class Program
    {
        static void Main(string[] args)
        {
            FeedBack fb = new FeedBack(new Program().FeedBackOne);
            fb += new FeedBack(Program.FeedBackTwo);
            fb += new FeedBack(new Program().FeedBackThree);
            new Program().RunIt(fb);
        }

        public void RunIt(FeedBack fb)
        {
            if (fb!=null)
            {
                fb(1);
            }
        }

        public void FeedBackOne(int value)
        {
            Console.WriteLine(value);
        }

        public static void FeedBackTwo(int value)
        {
            Console.WriteLine(value+1);
            Console.Read();
        }

        public void FeedBackThree(int value)
        {
            Console.WriteLine(value + 3);
        }
    }
  

 可以看出,委托要指定一个回调方法的签名。上面的RunIt方法,如果fb不为null,就调用由fb变量指定的回调方法。

而这里还用到了fb+=new FeedBack(*)的形式,这种用委托回调许多方法的形式叫委托链,我们稍后会讲到。

委托对象是方法的一个包装器(wrapper),使方法能通过包装器来间接回调。

将一个方法绑定到委托时,C#和CLR都允许引用类型的协变性和逆变性。协变性是指方法能返回从委托的返回类型派生的一个类型。逆变性是指方法获取的参数可以是委托的参数的基类。

如一下委托:

delegate object MyCallBack(FileStream s);

可以包装的方法:

string SomeMethod(Stream s);

注意,协变形和逆变性只能用于引用类型,不能用于值类型或void,所以,以上委托不能包装一下方法:

int SomeMethod(Stream s);

虽然int是继承自object,但int是值类型,这种形式的协变形是不允许的。因为值类型的存储结构是变化的,而引用类型的存储结构始终是一个指针。

通过上面的例子我们也发现了,委托既可以包装一个静态方法也可以包装一个实例方法,但它们是有区别的:

我们来看这段代码delegate void FeedBack(int value);

实际上,编译器会像下面这样定义一个完整的类:

class FeedBack:System.MulticastDelegate

{

public FeedBack(Object object,IntPtr method); //构造函数

public virtual void Invoke(int value); //这个方法和源代码指定的原型一样

        //以下方法实现了对回调方法的异步回调

        public virtual IAsyncResult BeginInvoke(int value,AsyncCallback callback,Object object);

        public virtual void EndInvoke(IAsyncResult result);

}

所以你也看到了,由于委托是类,所以凡是能够定义类的地方,都能定义委托。

所有委托都继承了MulticastDelegate的三个非公共字段:_target、_methodPtr、_invocationList

你也看到了,所有委托的个构造函数都是接受Object object,IntPtr method这俩参数,但为何我们却可以这样构造委托呢:FeedBack fb = new FeedBack(new Program().FeedBackOne);

原来,C#编译器知道要构造的是委托,会分析源代码来确定绑定的是哪个对象的哪个方法,如这里的对象new Program()的引用被传给了object,然后在构造函数里将object赋给_target,对于静态方法,object会被赋为null;至于方法,标识了方法的一个特殊IrntPt值(从MethodDef或MedthodRef元数据token获得)被赋值给method,然后赋值给了_methodPtr。至于_invocationList,我们会在讲委托链的时候讲到。

这样以来,委托既然知道了,哪个对象的哪个方法,自然就可以调用该方法了。

而且,我们还可以通过委托的两个属性Target和Method属性(有一个内部转换机制,返回一个MethodInfo对象)来分别获得回调函数所在的对象及回调函数本身,我们可以使用它们来完成很多事情,如:可检查委托对象引用的是不是一个特定类型中定义的实例方法:

Boolean DelegateRefersToInstanceMethodOfType(MulticastDelegate d,Type type)

{

return ((d.Target!=null)&&(d.Target.GetType()==type));

}

再如,检查回调方法是否有一个特定名称:

Boolean DelegateRefersToInstanceMethodOfName(MulticastDelegate d,string name)

{

return (d.Method.Name==name);

}

实际上,上面的代码fb(1)可以写成fb.Invoke(1),Invoke被调用的时候,它是使用私有字段_target和_methodPtr在指定对象上调用包装好的回调方法。

 

我们再来谈谈委托链,委托链是由委托对象构成的一个集合。利用委托链可以调用集合中的委托所代表的全部方法。

再来看我们上面的代码:

            FeedBack fb = new FeedBack(new Program().FeedBackOne);

            fb += new FeedBack(Program.FeedBackTwo);

            fb += new FeedBack(new Program().FeedBackThree);

然后再在RunIt方法中调用fb,就会顺序调用这三个方法。

当FeedBack fb = new FeedBack(new Program().FeedBackOne);时,会构造一个委托对象,这时fb的_invocationList字段为null,然后 fb += new FeedBack(Program.FeedBackTwo);这时它的_invocationList字段

初始化为一个含有两个元素的委托对象数组,[0]指向new FeedBack(new Program().FeedBackOne),[1]指向new FeedBack(Program.FeedBackTwo),当再执行 fb += new FeedBack(new Program().FeedBackThree);时_invocationList字段就变成了拥有3个元素的委托对象数组了。

然后在RunIt方法里执行fb(1)时,该委托发现私有字段_invocationList不为null,所以就会循环遍历数组中的每个元素,并依次调用每个委托包装的方法。

这里有一个问题,如果我们这样定义委托:delegate int FeedBack(int value);

那么,我们把以上代码稍加改动,所以我们再次使用委托链时,我们只会得到最后一个委托的返回值,前面的返回值会被丢弃。还有一个问题就是,如果委托链中有一个抛出了异常或阻塞了相当长时间,会导致后面的所有委托都不执行。

还好,MulticastDelegate类提供了一个实例方法,GetInvocationList用于显示调用链中每一个委托。

GetInvocationList方法返回一个由Delegate引用构成的数组,其中每个引用都指向链中的一个委托对象。在内部,GetInvocationList构造并初始化一个数组,让它的每个元素都引用链中的一个委托,然后返回该数组的一个引用。如果_invocationList字段为null,返回的数组只有一个元素,该元素引用链中唯一一个委托,也就是委托实例本身。

因此RunIt方法可以做如下改动

 

public void RunIt(FeedBack fb)
        {
            if (fb!=null)
            {
                //fb(1);
                Delegate[] fbChain = fb.GetInvocationList();
                foreach (FeedBack item in fbChain)
                {
                    try
                    {
                        item(1);
                    }
                    catch (Exception)
                    {                    
                        throw;
                    }
                }
            }
        }

 这里要注意的是  foreach (FeedBack item in fbChain)而不是   foreach (Delegate item in fbChain)

 

试想我们经常想上面FeedBack那样定义一些委托,那么,我们的系统里将充斥着大量委托。实际上现在.Net给我们提供了17个Action委托,它们从无参数一直到16个参数。如果方法需要获取16个以上的参数,就必须定义自己的委托类型了:

public delegate void Action();

public delegate void Action<T>(T obj);

...

public delegate void Action<T1,T2,...T16>(T1 arg1,T2 arg2,...T16 arg16);

所以我们其实不用自己定义一个委托了,可以如下:

    Action<int> ac = new Action<int>(new Program().FeedBackOne);

            ac += new Action<int>(Program.FeedBackTwo);

            ac += new Action<int>(new Program().FeedBackThree);

            ac(1);

对于有返回值的方法,可以使用.Net提供的17个Func委托,也是从无参数到16个参数。其中TResult是返回值的类型。

public delegate TResult Func<TResult>();

public delegate TResult Func<T,TResult>(T arg);

...

public delegate TResult Func<T1,T2,...T16,TResult>(T1 arg1,T2 arg2,...T16 arg16);

如下:

      Func<int, int> fc = new Func<int, int>(new Program().FuncTest);

            int a = fc(3);

        public int FuncTest(int value)

        {

            return value;

        }

但是,如果需要使用ref或out关键字,以引用的方式传递一个参数,就需要自己定义委托了:

delegate void Bar(ref int z);

还有一些情形也需要自己定义委托:

1. 委托需要parmas关键字获取可变数量的参数  2. 委托有的参数需要默认值

3. 要对委托的泛型参数进行约束:

delegate void EventHandler<TEventArgs>(Object sender,TEventArgs e):where TEventArgs:EventArgs;

 

C#编译器还为委托提供了许多C#语法糖

1.不需要构造委托对象,如:

button1.Click+=button1_Click;  而不需要 button1.Click+=new EventHandler(button1_Click);

上面那个Func的例子也可以改成:

  Func<int, int> fc = new Program().FuncTest;

  int a = fc(3);

2.不需要定义回调方法(直接使用lambda表达式充当回调方法,因为lambda表达式就是一种匿名方法),如:

上面那个Func的例子也可以改成:

  Func<int, int> fc = (value)=>{return value;};

  int a = fc(3);

lambda表达式可以在编译器预计会看到一个委托的地方使用

 

其实上面的这俩一个比一个精简的语法糖,都是有C#编译器帮我们写了代码,编译器会声明一个委托,如果用的是lambda表达式,它还会帮我们写一个匿名方法(把我们lambda表达式要完成的东西写到这个方法里,方法名是以<开头的,并且每次编译其名字都不一样,且其是私有的private,如果我们的lambda表达式里没有访问任何实例成员,那么这个匿名方法将会是静态的static,否则将会是非静态的,静态的更高效一些,因为前面也讲了对于静态方法,这里的fc的_target是null),把这个匿名方法当成回调函数包含在委托里。

 

对于lanmda表达式,有一些规则必须要注意:

如果委托不获取任何参数就用()

Func<string> f=()=>"123";

编译器可以推断类型

Func<int,int,string> f=(int a,int b)=>(a+b).toString();  可写成

Func<int,int,string> f=(a,b)=>(a+b).toString();

获取一个参数可省略()

Func<int,string> f=n=>n.toString();

如果委托有ref/out参数,必须显示指定ref/out和类型

Bar b=(out int b)=>b=5;   委托定义:delegate Bar(out int a);

主体由两个或多个语句构成,必须用大括号把它们封闭起来。

 

3.局部变量可直接传给回调函数

            int a = 3;

            Func<int, int> fc = (value) => { return value+a; };

            int c=fc(5); 

不要觉得这是顺理成章的是,如果没有这个C#语法糖,这可是相当枯燥和麻烦的工作。而且上面也提到了,由于这里我们访问了一个实例成员a,所以C#编译器为我们生成的这个匿名方法将会是私有非静态的。

 

还有些情形是,也许我们在写代码的时候(当然编译时也是这样)都不能确定我们要调用哪个委托,传哪些参数给委托的回调方法。还好,System.Delegate给我们提供了如下方法,通过与反射结合,来实现:

构造用于包装指定静态方法的一个“Type”委托

public static Delegate CreateDelegate(Type type,MethodInfo method)

public static Delegate CreateDelegate(Type type,MethodInfo method,Boolean throwOnBindFailure)

构造用于包装指定实例方法的一个“Type”委托

public static Delegate CreateDelegate(Type type,Object firstArgument,MethodInfo method)

public static Delegate CreateDelegate(Type type,Object firstArgument,MethodInfo method,Boolean throwOnBindFailure)

调用委托并传递参数

public Object DynamicInvoke(params Object[] args)

所有CreateDelegate方法构造的都是从Delegate派生的一个类型的新对象,具体类型由第一个参数type来决定。MethodInfo参数指出应该回调的方法,要用反射API来获取这个值。如果希望委托包装一个实例方法,还要传一个firstArgument,指定应作为this参数(第一个参数)传给实例方法的对象。如果委托不能绑定到method参数指定的方法,通常会抛出一个ArgumentException异常,可以指定throwOnBindFailure为false,不会抛异常,而是返回null。

 public delegate int Plus(int a, int b);
 public delegate string Getstr(string str);

    class Program
    { 
        static void Main(string[] args)
        {
            string whichone = Console.ReadLine();
            Type type = null;
            object[] parmas=new object[2];
            if (whichone=="Plus")
            {
                type = Type.GetType(whichone);
                for (int i = 0; i < 2; i++)
                {
                    parmas[i] = i;
                }
            }
            if (whichone=="Getstr")
            {
                type = Type.GetType(whichone);
                for (int i = 0; i < 2; i++)
                {
                    parmas[i] = i.ToString();
                }
            }
            MethodInfo method = typeof(Program).GetMethod(whichone+"Method");
            Delegate d = Delegate.CreateDelegate(type, method);
            object a = d.DynamicInvoke(parmas);
            Console.WriteLine(a);
            Console.Read();
        }

        public static int PlusMethod(int a, int b)
        {
            return a + b;
        }

        public static string GetstrMethod(string str)
        {
            return str;
        }
    }

 以上代码type老是为null 待解决中

http://tianmoboping.blog.163.com/blog/static/15739532201010795120518/

http://bbs.csdn.net/topics/230013406

http://q.cnblogs.com/q/30775/

 

补:关于Type.GetType()及某实例.GetType()  typeof(某类型)

lookhere后面可知,对象的GetType()方法返回的就是该对象中指向类型对象的类型对象指针(简单的讲就是获得了该对象的类型对象Type)然后即便是在运行时我们也可以通过反射获取关于该类型的一切了。

而 new Program().GetType()与typeof(Program) 完全是一样的

而Type.GetType()要麻烦些,至少要指定某类型的完整名称,包括命名空间

http://blog.csdn.net/byondocean/article/details/6550457

http://kendezhu.iteye.com/blog/15389672

分享到:
评论

相关推荐

    检 验 委 托 单.doc

    【检 验 委 托 单】是企业质量管理中的一个重要文档,它在IT行业中尤其是在供应链管理和质量控制环节起到关键作用。以下是该文档涉及的知识点及其详细解释: 1. **检验委托流程**:检验委托单是启动产品质量检验...

    授 权 委 托 书.doc

    授 权 委 托 书.doc

    委 托 授 权 书

    一般性的授权委托书,是平时代理公司或者个人办理正式业务(如:金融、通讯、政府)时必须提供的一种文书!

    委 托 书.docx

    在IT行业中,委托书是一种非常重要的法律文档,尤其在处理业务代理、项目管理或软件开发等事务时。委托书能够明确地定义委托人与被委托人之间的权利与责任,确保双方在合作过程中的权益得到保障。...

    试 验 委 托 单.doc

    【试验委托单】是IT行业中常见的一种文档,主要用于在实验室测试、软件测试或硬件验证等场景下,由需求方(委托单位)向提供测试服务的机构(检验科)提交的正式请求。这份文档包含了关于试验的基本信息,确保双方对...

    个人授 权 委 托 书2.doc

    《个人授权委托书》是公民在日常生活中常见的一种法律文件,用于个人因故无法亲自处理某项事务时,授权他人代为行事。这份文档的重要性在于它明确了授权的范围、期限以及双方的权利和责任,保障了委托人和受托人的...

    委 托 书[整理].doc

    在IT行业中,委托书是一种重要的法律文件,尤其在涉及到数据处理、项目管理或者软件开发等业务时,可能会涉及权限委托的问题。以下是对标题和描述中所述知识点的详细说明: 委托书,也称为授权书,是委托人(通常是...

    授 权 委 托 书a.doc

    在IT行业中,授权委托书可能涉及到的技术层面主要集中在数据安全、身份验证、权限管理以及电子签名等方面。以下是对这些知识点的详细说明: 1. **数据安全**:在电子文档如"授权委托书a.doc"中,确保数据的安全是至...

    参考合同-委 托 拍 卖 合 同.zip

    《委托拍卖合同》是拍卖活动中常见的一种法律文件,它规定了委托人与拍卖人之间的权利、义务和责任。本文将详细解析这份文档中涉及的主要知识点。 一、合同的基本概念 在法律体系中,合同是一种民事法律行为,是...

    合同模板协议范文2021委 托 拍 卖 合 同.docx

    不得将标的另委他人拍卖;成交后需协助买受人处理相关手续;可以选择是否要求拍卖人保密保留价;并设定了争议解决方式,包括协商、仲裁或诉讼。 9. **联系方式**:合同最后列出了委托人的住所、法定代表人、代理人...

    法 人 授 权 签 订 合 同 委 托 书汇编.pdf

    《法人授权签订合同委托书详解》 法人授权签订合同委托书是企业在进行重大交易或工程项目时,由法定代表人出具的法律文件,用以明确授权某位员工或代理人代表企业签署合同的权利。这样的委托书旨在确保交易的合法性...

    4300受(委)托代管存货询证函.doc

    《受(委)托代管存货询证函》是企业审计过程中的一项重要文件,主要用于确认企业间关于存货保管、加工和销售的准确性。在本文中,我们将深入探讨这个询证函的意义、作用以及相关的会计原则。 首先,存货是企业的...

    海关罚没收入托付缴库通知书.docx

    授 权 委 托 书是指授 权 委 托 书的规定和要求。该委托书规定了托付人的权利和义务,包括托付事项、托付权限、托付时限、委托人和被托付人的责任等。 报关托付书 报关托付书是指报关托付书的规定和要求。该委托书...

    C#中的委托和事件(完整版)

    委托和事件在.NET Framework 中的应用非常...托、事件的由来、.NET Framework 中的委托和事件、委托中方法异常和超时的处理、委托与异步编程、委 托和事件对Observer 设计模式的意义,对它们的编译代码也做了讨论。 1.1

    C#-WinForm跨线程修改UI界面的实例

    在C#编程中,Windows Forms(WinForm)应用程序的用户界面(UI)是单线程的,由主线程管理。这意味着所有的UI更新必须在主线程中执行,以确保UI的一致性和正确性。当需要在后台线程或者新线程中执行耗时操作时,直接...

    GB∕T 28449-2018 信息安全技术网络安全等级保护测评过程指南

    托测 评 机 构 开 展 等 级 测 评, 对 信 息 安 全 管 控 能 力 进 行 考 察 和 评 价, 从 而 判 定 定 级 对 象 是 否 具 备 GB/T22239中相应等级要求的安全保护能力。 因此, 等级测评活动所形成的等级测评报告是...

    推荐上市非金融企业影子银行指标数据整理(2007-2022年)

    非金融企业影子银行指标 计算说明 ...托贷款、委托理财、民间借贷规模和类金融资产之和除以总资产 参考文献 [1]韩珣, 易祯.货币政策、非金融企业影子银行业务与信贷资源配置效率[J].财贸经济,202 3,44(01)

    物联网的四大部署方式.doc

    可能由机构或其委 托的第三方实 施和维护,主要存在于机构内部(OnPremise)内网(Intranet)中,也可存在于机 构外部(OffPremise)。 2.公有物联网(PublicIoT):基于互联网(Internet)向公众或大型用户群体提供 ...

    武汉市仲裁委员会授权委托书.pdf

    授 权 委 托 书是指委托人授权受委托人在仲裁活动中代理其权益的书面协议。该协议规定了委托人的身份信息、受委托人的身份信息、授权的范围和权限、委托人的义务等内容。 委托人是指在仲裁活动中拥有权益的当事人,...

Global site tag (gtag.js) - Google Analytics