委 托

回调函数是一种非常有用的编程机制,它的存在已经有很多年了。.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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值