全文摘自

http://www.cnblogs.com/xun126/archive/2010/12/30/1921551.html

写得不错,特意备份!并改正其中的错误代码..

    正文:

最近在学C#的委托,通过网络的资料和书籍,现在总结如下:

回调(Callback)函数是windows编程的一个重要部分。回调函数实际上是方法调用的指针,也成为函数指针,是一个非常强大的编程特

回调(Callback)函数是windows编程的一个重要部分。回调函数实际上是方法调用的指针,也成为函数指针,是一个非常强大的编程特性。.NET以委托的形式实现了函数的指针的概念。与C/C++的函数指针不同的是.NET委托是类型安全的。也就是说C/C++的函数指针只不过是一个指向内存单元的指针,我们无法知道这个指针实际指向什么,像参数和返回类型等就无从知晓了。

当把方法传送给其他方法时,需要用到委托。如考虑以下的函数:

  C++:

    #include <iostream>

    #include <string>

    using namespace std;

    int fun(int);

    int fun_call(int (*f)(int),int);

    void main(int argc,char* argv[])

    {

        typedef int (*fp)(int);

        fp fpt;

        fpt=fun;

        count<<fun_call(fpt,1);

    }

    int fun(int a)

    {

        return a-1;

    }

    int fun_call(int (*fp)(int),int b)

    {

        return (fp(10)+b);

    }

  上述程序的“ftp=fun”实现函数指针的初始化,直接将fun的地址赋给函数指针ftp,然后传送给fun_call,fun_call可以根据这两个参数计算出结果:fp(10)=9,9+1=10。实现了把方法传送给其他方法。

  函数指针最常用的是使用函数指针数组来批量调用函数:

      int f1(){return 1;}

     int f2(){return 2;}

      int f3(){return 3;}

    void main(int argc,char* argv[])

      {

        tpyedef int (* fp)();

        fp fps[3]={f1,f2,f3};

        for(int 0;i<2;i++)

        {

          cout<<fps[i]<<endl;    //实现按数组序列号调用函数

        }

      }

  在编译时我们不知道第二个方法会是什么,这个信息只能在运行时得到,所以需要把第二个方法作为参数传递给第一个方法。在C/C++,只能提取函数的地址,并传送为一个参数。c是没有类型安全性的,可以把任何函数传送给需要函数指针的方法。这种直接的方法会导致一些问题,例如类型安全性,在面向对象编程中,方法很少是孤立存在的,在调用前通常需要与类实例相关联。而这种指针的方法没考虑这种情况。所以.NET在语法上不允许使用这种直接的方法。如果要传递方法,就必须把方法的细节封装在一种新的类型的对象中,这种新的对象就是委托。

  委托,实际上只是一种特殊的对象类型,其特别之处在于,我们之前定义的所有对象都包含数据,而委托包含的只是函数的地址。

  1、在c#中声明委托

  delegate void Method(int x);

  定义了委托就意味着告诉编译器这种类型的委托代表了哪种类型的方法,然后创建该委托的一个或多个实例。编译器在后台将创建表示该委托的一个类。也就是说,定义一个委托基本上是定义一个新类,所以可以在定义类的任何地方定义委托,既可以在类的内部定义,也可以在类的外部定义。注意,委托是类型安全性非常高的,因此定义委托时,必须给出它所代表的方法签名和返回类型等全部细节。

  2、在C#中使用委托

    using System;

    namespace DelegateSpace

    {

        class DelegateTest

        {

            private delegate string GetString(int x,int y);

            static void Main()

            {

               Test test=new Test();

               GetString method=new GetString(test.Add);

               Console.WriteLine(method());

            }

        }

        class Test

        {

            public string Add(int x,int y)

            {

               return (x+y).ToString();

            }

        }

    }

    上述程序中声明了类型为GetString的委托,并对它初始化,使它指向对象test的方法Add(int x,int y)。在C#中,委托在语法上总是带有一个参数的构造函数,这个参数就是委托指向的方法,这个方法必须匹配最初定义委托时的签名。如上例中委托是这样定义的:“delegate string GetString();”要求被委托的函数的返回类型是string,如果test.Add(int x,int y)返回的是int,则编译器就会报错。还要注意赋值的语句:“GetString method=new GetString(test.Add);“不能写成"GetString method=new GetString(test.Add(3,2));"因为test.Add(2,3)返回的是string。而委托的构造函数需要把传进的是函数的地址,这很像C/C++的函数指针。

  3、多播委托

  调用委托的次数与调用方法的次数相同,如果要调用多个方法,就需要多次显式调用这个委托。委托也可以包含多个方法。这种委托称为多播委托。如果调用多播委托,就可以按顺序连续调用多个方法。所以,委托的签名必须返回void,否则,就只能得到委托调用的最后一个方法的结果。

  如:

    delegate void DoubleOp(double value);

    class MainEntry

    {

        static void Main()

        {

            DoubleOp operations=MathOperation.MultiplyByTwo;

            operations+=MathOperation.Square;

        }

    }

    class MathOperation

    {

        public static double MultiplyByTwo(double value)

        {

            return value*2;

        }

        public static double Square(double value)

        {

            return value*value;

        }

    }

  上面的“DoubleOp operation=MathOperation.MultiplyByTwo;operation+=MathOperation.Square;” 等价于“DoubleOp operation1=MathOperation.MultiplyByTwo;DoubleOp operation2=MathOperation.Square;DoubleOP operations=operation1+operation2;”,多播委托还可以识别运算符-和-=,用于从委托中删除方法调用。

  通过一个多播委托调用多个方法还有一个大问题。多播委托包含一个逐个调用委托的集合。如果通过委托调用一个方法抛出异常,整个迭代就会终止。在这种情况下,为了避免这个问题,应手动迭代方法列表。可以使用Delegate类定义的方法GetInvocationList(),它返回一个Delegate对象数组。

  如考虑以下代码:

    public delegate void DemoDelegate();

    class Program

    {

        static void One()

        {

            Console.WriteLine("One");

            throw new Exception("Test!");

        }

        static void Two()

        {

            Console.WriteLine("Two");

        }

        static void Main()

        {

            DemoDelegate dl=One;

            dl+=Two;

            try

            {

                dl();

            }

            catch(Exception)

            {

                Console.WriteLine("Exception caught!");

            }

        }

    }

    运行结果:

    One

    Exception caught!

    修改后的代码如下:

    static void Main()

    {

        DemoDelegate dl=One;

        dl+=Two;

        Delegate[] delegates=dl.GetInvocationList();

        foreach(DemoDelegate d in delegates)

        {

            try

            {

                d();

            }

            catch(Exception)

                 {

            Console.WriteLine("Exception caught");

                }

        }

       

    }

    运行结果如下:

    One

    Exception caught

    Two

  同样地,如果委托签名不是返回void,但希望得到所有的经委托调用后的结果,也可以用GetInvocationList()得到Delegate对象数组,再用上面的迭代方式获得返回结果。

  4、匿名方法

  使用委托还有另外一种方式:通过匿名方法。匿名方法是用作委托参数的一个代码块。

  如下代码:

  using System;

  namespace DelegateTest

  {

    class Program

    {

        delegate string delegateString(string val);

        static void Main()

        {

            string mid=",middle part";

            delegateString anonDel=delegate(string param)

            {

                param+=mid;

                param+=" and end";

                return param;

            };

            Console.WriteLine(anonDel("Strat of string"));

        }

    }

  }

  该代码块使用方法级的字符串变量mid,该变量是在匿名方法的外部定义的,并添加到要传送的参数中,接着代码返回该字符串值。匿名方法的优点是减少要编写的代码。不必定义仅由委托使用的方法。在为事件定义委托时,这是很显然的。这有助于减低代码的复杂性,尤其是定义了好几个事件时,代码会显得比较简单。使用匿名方法时,代码执行得不太快。

  在使用匿名方法时,必须遵循一些规则:

  1)在匿名方法中不能使用跳转语句跳到该匿名方法的外部;

  2)匿名方法外部的跳转语句不能跳到该匿名方法的内部;

  3)在匿名方法内部不能访问不安全代码,也不能访问在匿名方法外部使用的ref和out参数,但可以使用在匿名方法外部定义的其他变量。

  5、λ表达式

  这是C# 3.0为匿名方法提供的一个新方法。如前面的语句:

  ...

  static void Main()

  {

    string mid=...;

    delegateString anonDel=param=>

    {

        param+=mid;

        param+=" and end";

        return param;

      };

    ...

  }

  ...

  λ表达式=>的左边列出了匿名方法需要的参数,右边列出了实现代码,实现代码放在花括号中,类似于前面的匿名方法,如果实现代码只有一行,可以删除花括号和return语句,编译器会自动添加该语句。

  如:public delegate bool Predicate(int val);

    Predicate pl=x=>x>5;

  在上面的λ表达式中,左边定义了变量x,这个变量的类型自动设置为int,因为这是通过委托定义的,实现代码返回比较x>5布尔结果。如果x大于5,则返回true,否则返回false。

  6、协变和抗变

  委托调用的方法不需要与委托声明的定义类型相同。由此出现协变和抗变。

  1)返回类型协变

  方法的返回类型可以派生于委托定义的类型。如下代码:

    public class DelegateReturn

    {

    }

    public class DelegateReturn2:DelegateReturn

    {

    }

    public delegate DelegaReturn MyDelegate1();

    class Program

    {

        static void Main()

        {

            MyDelegate1 d1=Method1;

            d1();

        }

        static DelegateReturn2 Method1()

        {

            DelegateReturn2 d2=new DelegateReturn2();

            return d2;

        }

    }

  上述代码中,委托MyDelegate定义为返回DelegateReturn类型。赋予委托实例d1的方法返回DelegateReturn2类型,DelegateReturn2派生自Delegate,根据子类“是”父类的这种关系,满足了委托的需求。这称为返回类型的协变。

  2)参数类型的抗变

  委托定义的参数可能不同于委托调用的方法,这里是返回类型不同,因为方法使用的参数类型可能派生自委托定义的类型。如下代码:

    public class DelegateParam

    {    

    }

    public class DelegateParam2:DelegateParam

    {

    }

    public delegate void MyDelegate2(DelegateParam2 p);

    class Program

    {

        static void Main()

        {

            MyDelegate2 d2=Method2;

            DelegateParam2 p=new DelegateParam2();

            d2(p);

        }

        static void Method2(DelegateParam p)

        {

        }

    }

  上述代码中,委托使用的参数类型是DelegateParam2,而赋予委托实例d2的方法使用的参数类型是DelegateParam,DelegateParam是DelegateParam2的基类。

《C#高级编程》之委托学习笔记 (转载)的更多相关文章

  1. 《JavaScript DOM 编程艺术》 学习笔记

    目录 <JavaScript DOM 编程艺术> 学习笔记 第一章 js简史 第二章 js语法 准备工作 语法 第三章 DOM DOM中的D DOM中的O DOM中的M 第四章 js图片库 ...

  2. 《Java编程思想》学习笔记(二)——类加载及执行顺序

    <Java编程思想>学习笔记(二)--类加载及执行顺序 (这是很久之前写的,保存在印象笔记上,今天写在博客上.) 今天看Java编程思想,看到这样一道代码 //: OrderOfIniti ...

  3. 委托学习笔记后续:泛型委托及委托中所涉及到匿名方法、Lambda表达式

    引言: 最初学习c#时,感觉委托.事件这块很难,其中在学习的过程中还写了一篇学习笔记:委托.事件学习笔记.今天重新温故委托.事件,并且把最近学习到和委托相关的匿名方法.Lambda表达式及泛型委托记录 ...

  4. 《高质量C++&C 编程指南》学习笔记

    这本电子书是在国科大上课时候,老师在课件资源里边提供的.之所以会重视这个文件,是因为本科时候,有个老师提到过:那个学生遍的代码很整齐,看起来让人舒服,我就知道工大留不下他.因此,我就格外注意这件事,但 ...

  5. 《Java并发编程实战》学习笔记 线程安全、共享对象和组合对象

    Java Concurrency in Practice,一本完美的Java并发参考手册. 查看豆瓣读书 推荐:InfoQ迷你书<Java并发编程的艺术> 第一章 介绍 线程的优势:充分利 ...

  6. java 方法调用绑定--《java编程思想》学习笔记

    将一个方法调用同一个方法主体关联起来,就是绑定. 绑定分两种 :前期绑定 和 后期绑定 . 绑定------------- | -----前期绑定-------编译期绑定 { static , fin ...

  7. js学习笔记—转载(闭包问题)

    ---恢复内容开始--- 闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现.     一.变量的作用域 要理解闭包,首先必须理解Javascrip ...

  8. 《JavaScript DOM编程艺术》学习笔记(一)

    这本书是我听说学习前端基础入门书籍,于是就开始看了,大概是从5月10号开始看的吧,一直看到现在,差不多要看完了,书是挺厚的...286页,不过比起JAVASCRIPT权威指南来说还是差多了,权威指南才 ...

  9. 《Java并发编程实战》学习笔记

    第2章 线程安全性 正确性: 某个类的行为与其规范完全一致. 2.1线程安全: 当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或 ...

随机推荐

  1. Android开发之解决父控件拦截子控件事件问题

    以ViewPager为例: public class TopNewsViewPager extends ViewPager { public TopNewsViewPager(Context cont ...

  2. MyBatis的foreach标签与SUM函数同时使用

    最近在项目中遇到一个,需要根据传入的存有id的list,计算值,再起别名 <if test="channelList != null and channelList.size()> ...

  3. JavaScript零基础学习系列二

    条件控制 if(条件){//语句块}如果条件(小括号里面的)满足true.那么才会执行大括号里面的代码,如果条件不满足(false),那么不执行,注意:有可能代码不会执行. 例如: if(3>1 ...

  4. get last dirname/filename in a file path argument

    $ dirname /home/train/00.incipient_data/data_for_gene_prediction_and_RNA-seq/240_rep2.fastq /home/tr ...

  5. 【BZOJ-3507】通配符匹配 DP + Hash

    3507: [Cqoi2014]通配符匹配 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 372  Solved: 156[Submit][Statu ...

  6. NGUI 滑动与点击事件冲突处理

    弄一个既能滑动,又能点击的Scroll View.发现弄完后不能拖动了~ 因为点击事件需要Box Collider覆盖掉了Drag Scroll View的Box Collider.注意是Drag S ...

  7. photoshop拾色器如何恢复默认?

    今天在做设计图的时候,遇到一个问题,当时就把我给整蒙了. 问题是这样的,ps的调色器变成了这样,如下: 本来应该是这样: 可能有人已经看出两张图的不同之处了. 但是我当时忙的不得了,恩是不知道哪里除了 ...

  8. 10月25日下午PHP静态、抽象、接口

    多态(运行多态)概念:当父类引用指向子类实例,由于子类里面对父类的方法进行了重写,父类引用在调用该方法的时候表现出的不同状态.条件:1.必须发生在继承下2.必须重写父类方法3.父类引用调用该方法 如果 ...

  9. iis搭建FTP服务器

    win7下如何开启iis请参考前一篇 使用iis并搭建 iis 图片服务器 ftp登陆格式  : ftp://[帐号]:[密码]@[IP]:[端口] ftp://用户名:密码@FTP服务器IP或域名: ...

  10. Python学习笔记——迭代器(RandSeq和AnyIter)

    1.RandSeq #coding:utf-8 #!/usr/bin/env python 'randSeq.py -- 迭代' #从random模块里仅仅导入choice方法 from random ...