• C#进阶之路(一):委托


    一、什么是委托

      简单说它就是一个能把方法当参数传递的对象,而且还知道怎么调用这个方法,同时也是粒度更小的“接口”(约束了指向方法的签名)

      委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,是种将方法动态地赋给参数的做法。

      用过C/C++的,对委托不会陌生,委托可以看成函数指针的升级版本!

      函数指针简介:

      下面是一段C程序,Calc就是定义的函数指针。

    typedef int (* Calc)(int a, int b);
    
    int Add(int a, int b)
    {
    int result = a + b;
    return result;
    }
    main()
    {
    int x = 100;
    
    int y = 200;
    
    int z = 0;
    
    Calc funcPoint1 = &Add;
    
    z = funcPoint1(x, y);
    
    printf("%d 
    ", z);
    }

    这段程序很好的体现了一切皆地址的思想,变量和函数都是地址。

    直接调用和间接调用的效果是一致的,都是访问那个内存地址,委托相当于函数指针的升级版。

      委托的简单案例

      一个委托类型定义了该类型的实例能调用的一类方法,这些方法含有同样的返回类型和同样参数(类型和个数相同)。

    委托是一个类,所以要在类声明的位置进行声明,而不是写在类里面,那样就写成嵌套类了。如下定义了一个委托类型 - Calculator:

    delegate int Calculator (int x);

    此委托适用于任何有着int返回类型和一个int类型参数的方法,如:

    static int Double (int x) { return x * 2; }

    创建一个委托实例,将该此方法赋值给该委托实例:

    Calculator c = new Calculator(Double);

    也可以简写成:

    Calculator c = Double;

    这个方法可以通过委托调用:

    int result = c(2);

    下面是完整代码:

    delegate int Calculator(int x);
    
    class Program {
        static int Double(int x) { return x * 2; }
        static void Main(string[] args) {
            Calculator c = Double;
        //c 就是委托实例,
            int result = c(2);
            Console.Write(result);
            Console.ReadKey();
        }
    }

    二、委托的一般使用

    2.1用委托实现插件式编程

      我们可以利用“委托是一个能把方法作为参数传递的对象”这一特点,来实现一种插件式编程。

      例如,我们有一个Utility类,这个类实现一个通用方法(Calculate),用来执行任何有一个整型参数和整型返回值的方法。这样说有点抽象,下面来看一个例子:

    delegate int Calculator(int x);
    
    //这里定义了一个委托
    class Program {
        static int Double(int x) { return x * 2; }
        static void Main(string[] args) {
            int[] values = { 1,2,3,4};
            Utility.Calculate(values, Double);
            foreach (int i in values)
                Console.Write(i + " "); // 2 4 6 8
            Console.ReadKey();
        }
    }
    
    class Utility {
    public static void Calculate(int[] values, Calculator c) {
    // Calculator c 是简单委托的变种写法,就是把实例化放在了形参定义的语句里
    //但是这个实例化具体对应的是什么方法,只有真的传入参数的时候才知道!
            for (int i = 0; i < values.Length; i++)
                values[i] = c(values[i]);
        }
    }

      这个例子中的Utility是固定不变的,程序实现了整数的Double功能。我们可以把这个Double方法看作是一个插件,如果将来还要实现诸如求平方、求立方的计算,我们只需向程序中不断添加插件就可以了。

      如果Double方法是临时的,只调用一次,若在整个程序中不会有第二次调用,那么我们可以在Main方法中更简洁更灵活的使用这种插件式编程,无需先定义方法,使用λ表达式即可,如:

    ...

    Utility.Calculate(values, x => x * 2);

    ...

    2.2多播委托

    一个委托实例不仅可以指向一个方法,还可以指向多个方法。例如:

    MyDelegate d = MyMethod1;
    // “+=” 用来添加,同理“-=”用来移除。
    d += MyMethod2;
    // d -= MyMethod2 

    调用时,按照方法被添加的顺序依次执行。注意,对于委托,+= 和 -= 对null是不会报错的,如:

    MyDelegate d;
    d += MyMethod1;// 相当于MyDelegate d = MyMethod1;

      为了更好的理解多播在实际开发中的应用,我用模拟瞬聘网的职位匹配小工具来做示例。在职位匹配过程中会有一段处理时间,所以在执行匹配的时候要能看到执行的进度,而且还要把执行的进度和执行情况写到日志文件中。在处理完一个步骤时,将分别执行两个方法来显示和记录执行进度。

      我们先定义一个委托(ProgressReporter),然后定义一个匹配方法(Match)来执行该委托中的所有方法。如下:

    public delegate void ProgressReporter(int percentComplete);
    public class Utility {
        public static void Match(ProgressReporter p) {
            if (p != null) {
                for (int i = 0; i <= 10; i++) {
                    p(i * 10);
                    System.Threading.Thread.Sleep(100);
    //线程暂停0.1s之后再继续运行程序!
                }
            }
        }
    }

    然后我们需要两个监视进度的方法,一个把进度写到Console,另一个把进度写到文件。如下:

    class Program {
        static void Main(string[] args) {
            ProgressReporter p = WriteProgressToConsole;
            p += WriteProgressToFile;
            Utility.Match(p);
            Console.WriteLine("Done.");
            Console.ReadKey();
        }
        static void WriteProgressToConsole(int percentComplete) {
            Console.WriteLine(percentComplete+"%");
        }
        static void WriteProgressToFile(int percentComplete) {
           System.IO.File.AppendAllText("progress.txt", percentComplete + "%");
        }
    
    }

    运行结果:

     

    看到这里,是不是发现你已然更加爱上C#了。

    2.3静态方法和实例方法对于委托的区别

      当一个类的实例的方法被赋给一个委托对象时,在上下文中不仅要维护这个方法,还要维护这个方法所在的实例。System.Delegate 类的Target属性指向的就是这个实例。(也就是要在内存中维护这个实例,也就是可能的内存泄漏)

      但对于静态方法,System.Delegate 类的Target属性是Null,所以将静态方法赋值给委托时性能更优。

    2.4泛型委托

    如果你知道泛型,那么就很容易理解泛型委托,说白了就是含有泛型参数的委托,例如:

    public delegate T Calculator<T> (T arg);

    我们可以把前面的例子改成泛型的例子,如下:

    public delegate T Calculator<T>(T arg);
    class Program {
        static int Double(int x) { return x * 2; }
        static void Main(string[] args) {
            int[] values = { 1, 2, 3, 4 };
            Utility.Calculate(values, Double);
            foreach (int i in values)
                Console.Write(i + " "); // 2 4 6 8
            Console.ReadKey();
        }
    }
    class Utility {
        public static void Calculate<T>(T[] values, Calculator<T> c) {
            for (int i = 0; i < values.Length; i++)
                values[i] = c(values[i]);
        }
    }

    2.5Func 和 Action 委托

    有了泛型委托,就有了能适用于任何返回类型和任意参数(类型和合理的个数)的通用委托,Func 和 Action。如下所示(下面的in表示参数,out表示返回结果):

    delegate TResult Func <out TResult> ();

    delegate TResult Func <in T, out TResult> (T arg);

    delegate TResult Func <in T1, in T2, out TResult> (T1 arg1, T2 arg2);

    ... 一直到 T16

    delegate void Action ();

    delegate void Action <in T> (T arg);

    delegate void Action <in T1, in T2> (T1 arg1, T2 arg2);

    ... 一直到 T16

    有了这样的通用委托,我们上面的Calculator泛型委托就可以删掉了,示例就可以更简洁了:

    public static void Calculate<T>(T[] values, Func<T,T> c) {
        for (int i = 0; i < values.Length; i++)
            values[i] = c(values[i]);
    }
    //Func 是对delegate的一种简写,更简洁

    Func 和 Action 委托,除了ref参数和out参数,基本上能适用于任何泛型委托的场景,非常好用。ACTION 和FUNC 最常用的两种委托,类库为我们准备好的!

      action就是一种委托的简便写法,默认的是无返回值类型的方法,注意不要加括号,只是绑定地址,而不是执行!Func这种用来调用有返回值的委托!

    直接调用方法,使用calculator.report();

    间接调用,使用action.Invoke();

    action();这种写法是为了模仿函数指针的写法。实际上还是默认调用invoke()

    上面的案例写的有些乱,而且是一种把func写入函数参数的类型,不容易理解,下面再用另一个案例

    static void Main(string[] args)
    {
        Func<string> RetBook = new Func<string>(FuncBook);
        Console.WriteLine(RetBook());
    }
    public static string FuncBook()
    {
        return "送书来了";
    }

    无返回值

    static void Main(string[] args)
    {
        Action<string> BookAction = new Action<string>(Book);
        BookAction("百年孤独");
    }
    public static void Book(string BookName)
    {
        Console.WriteLine("我是买书的是:{0}",BookName);
    }

    2.6委托的异步调用

    1、显式异步调用

    显式异步调用 thread  或者 task

     

    2、隐式异步调用

    使用委托进行隐式异步调用,begininvoke就是隐式异步调用,它会开发分支线程,他有两个参数。

    aciont1.BeginInvoke(null, null);

    EndInvoke

    隐式调用也有两种,一种是不使用回调函数的,另一种是使用的。

    不使用回调函数

    namespace delegate3
    {
        class Program
        {
            //public delegate int AddHandler(int a, int b);
            public class Cal
            {
                public static int Add(int a, int b)
                {
                    Console.WriteLine("开始计算:" + a + "+" + b);
                    Thread.Sleep(3000); //模拟该方法运行三秒
                    Console.WriteLine("计算完成!");
                    return a + b;
                }
            }
            static void Main(string[] args)
            {
                Console.WriteLine("===== 异步调用 AsyncInvokeTest =====");
                //AddHandler handler = new AddHandler(Cal.Add);
                //IAsyncResult: 异步操作接口(interface)
                //BeginInvoke: 委托(delegate)的一个异步方法的开始
                //IAsyncResult result = handler.BeginInvoke(1, 2, null, null);
                Console.WriteLine("继续做别的事情。。。");
    
                Func<int,int,int> RetBook = new Func<int,int,int>(Cal.Add);
                //RetBook.BeginInvoke(1, 2);
                IAsyncResult result = RetBook.BeginInvoke(1, 2, null, null);
                //异步操作返回
                Console.WriteLine(RetBook.EndInvoke(result));
                Console.ReadKey();
            }
        }
    }
    View Code

    使用回调函数

    namespace CallBack
    { 
        public delegate int AddHandler(int a, int b);
        public class Cal
        {
            public static int Add(int a, int b)
            {
                Console.WriteLine("开始计算:" + a + "+" + b);
                Thread.Sleep(3000); //模拟该方法运行三秒
                Console.WriteLine("计算完成!");
                return a + b;
            }
        }
        class Program
        {
            static void Main()
            {
                Console.WriteLine("===== 异步回调 AsyncInvokeTest =====");
                AddHandler handler = new AddHandler(Cal.Add);
                //异步操作接口(注意BeginInvoke方法的不同!)
                IAsyncResult result = handler.BeginInvoke(1, 2, new AsyncCallback(回调函数), "AsycState:OK");
                Console.WriteLine("继续做别的事情。。。");
                Console.ReadKey();
            }
            static void 回调函数(IAsyncResult result)
            {
                //result 是“加法类.Add()方法”的返回值
                //AsyncResult 是IAsyncResult接口的一个实现类,空间:System.Runtime.Remoting.Messaging
                //AsyncDelegate 属性可以强制转换为用户定义的委托的实际类。
                AddHandler handler = (AddHandler)((AsyncResult)result).AsyncDelegate;
                Console.WriteLine(handler.EndInvoke(result));
                Console.WriteLine(result.AsyncState);
            }
        }
    }
    View Code

    三、委托的缺点

     

    引用了某个方法,那么这个方法在内存中就不能释放了,一旦释放,委托就不能调用这个方法,所以委托有可能造成内存泄漏。(静态方法不存在这个问题)

     

  • 相关阅读:
    [转]Ctags 使用细节
    [转]ctags的使用及相关参数介绍
    [转]ubuntu面板 图标缺失的处理办法
    压缩空气动力自行车
    丰富的开发体验和激动人心的用户体验:XAML
    发现一个控件,介绍一下
    智能电视的设想(发明畅想)
    裹脚布
    整理了《类库开发的设计准则》一文
    关于设计器类程序的模型,先记录下来,怕以后忘记了
  • 原文地址:https://www.cnblogs.com/qixinbo/p/8297314.html
Copyright © 2020-2023  润新知