C#委托

委托是C#重要特性之一,C#中很多特性都是建立在委托的基础之上。

什么是委托

委托也叫代理delegate,就是把事情交给别人去办理,例如委托律师代理打官司,委托同学代买火车票等。

法庭上律师为当事人进行辩护,律师真正执行的是当事人的陈词,律师就是一个委托对象,当事人则委托律师进行辩护。

C#中如果将一个方法委托给一个对象,这个对象就可以全权代理这个方法的执行。

函数指针

C#的委托可理解为函数的一个包装,使得C#中的函数可以作为参数进行传递,作用上相当于C/C++中的函数指针。如果函数指针想要指向某函数,函数指针的返回值类型和指向的函数的返回值类型必须相同,并且参数相同。

委托delegate函数指针的升级版,函数指针是C/C++语言中特有的功能。

$ vim delegate.c
#include <stdio.h>
#include <stdlib.h>

//定义函数指针类型
typedef int(*Calc)(int x, int y);

int add(int x, int y)
{
    return x+y;
}

int sub(int x, int y){
    return x-y;
}

int main()
{
    int x = 100;
    int y = 200;
    int z = 0;

    //函数调用,直接调用。
    z = add(x,y);
    printf("%d + %d = %d\n", x, y, z);

    z = sub(x,y);
    printf("%d - %d = %d\n", x, y, z);

    //定义函数指针,间接调用。
    Calc fnp1 = &add;
    Calc fnp2 = &sub;
    
    z = fnp1(x, y);
    printf("%d + %d = %d\n", x, y, z);

    z = fnp2(x, y);
    printf("%d + %d = %d\n", x, y, z);

    system("pause");
    return 0;
}

$ gcc delegate.c -o delegate.exe
$ delegate.exe
100 + 200 = 300
100 - 200 = -100
100 + 200 = 300
100 + 200 = -100

定义委托

使用委托首先要定义委托,声明委托能代理什么类型的方法,类似房产中介能代理抵押贷款业务,而不能代理打官司一样。

委托的定义和方法的定义类似,只是在定义前多了一个delegate关键字。

定义委托的语法

<访问修饰符> delegate 返回类型 委托名称();

示例

public delegate void DelagateName(int intParam, string strParam)

委托能包装的方法有一定限制

委托类型DelegateName包装的方法需满足条件

  1. 方法的返回值类型必须为void
  2. 方法必须有两个参数,第一个参数必须为int类型,第二个参数必须为string类型。

声明委托

如果要把方法当做参数来传递的话,就要用到委托。简单来说,委托就是一个类型,这个类型可以赋值一个方法的引用。

C#中使用类分为两个阶段,首先定义类,即告诉编译器这个类由什么字段和方法组成,然后使用类实例化后的对象。在使用委托时,也需要经历两个阶段,首先定义委托,即告知编译器这个委托可以指向那些类型的方法,然后创建该委托的实例。

使用delegate定义委托,定义委托需定义所要指向方法的参数和返回值。

//定义叫IntMethodInvoker的委托,指向带有int类型参数的方法且返回值为void。
delegate void IntMethodInvoker(int x);

delegate double TwoLongOp(long first, long second);

delegate string GetString();

使用委托

using System;

namespace CSharp
{
    class Program
    {
        //声明委托:定义委托类型,委托类型名称为GetString。
        private delegate string GetString();
        //入口主函数
        static void Main(string[] args)
        {
            /*使用委托调用ToString方法*/
            int x = 100;

            //声明委托:使用委托类型创建实例
            //GetString method 使用委托声明一个类型叫做method
            //new GetString(x.ToString) 使用new对其进行初始化,使其引用到x中的ToString()上,这样method就相当于x.ToString。
            //ToString方法用来把数据转换成字符串
            //GetString method = new GetString(x.ToString);

            //初始化委托:直接将方法赋值为委托类型的变量
            GetString method = x.ToString;

            //通过委托实例调用方法,通过Invoke方法调用method所引用的方法。
            //string result = method.Invoke();  

            //通过委托实例调用方法 简写
            //通过method()执行方法就相当于x.ToString()
            string result = method();

            Console.WriteLine(result);//100

            Console.ReadKey();
        }
    }
}

将委托类型作为方法的参数使用

using System;

namespace CSharp
{
    class Program
    {
        /*定义委托*/
        private delegate void PrintString();
        /*使用委托作为方法的参数*/
        static void PrintMethod(PrintString print)
        {
            print();
        }
        //静态方法
        static void Method1()
        {
            Console.WriteLine("Method1");
        }
        //静态方法
        static void Method2()
        {
            Console.WriteLine("Method2");
        }
        //入口主函数
        static void Main(string[] args)
        {
            //定义委托变量并初始化
            PrintString method = Method1;
            //将委托变量作为方法参数
            PrintMethod(method); //Method1

            method = Method2;
            PrintMethod(method); //Method2         

            Console.ReadKey();
        }
    }
}

综合案例

using System;

namespace Test
{
    class Operate
    {
        /*数组按降序排序*/
        public static bool ArraySort(int[] arr)
        {
            for(int i=arr.GetUpperBound(0); i>=0; i--)
            {
                for(int j=0; j<i; j++)
                {
                    if(arr[j] <= arr[i])
                    {
                        Swap(ref arr[j], ref arr[i]);
                    }
                }
            }
            return true;
        }
        /*交换两个变量的值*/
        public static void Swap(ref int x, ref int y)
        {
            int tmp = x;
            x = y;
            y = tmp;
        }
    }
    class Program
    {
        /*定义委托*/
        public delegate bool DelegateSort(int[] arr);
        static void Main(string[] args)
        {
            int[] arr = new int[] { 3, 2, 9, 2, 4, 1 };

            Console.WriteLine("排序前");
            foreach (int i in arr)
            {
                Console.WriteLine(i);
            }

            //声明委托变量
            DelegateSort delegateSort;
            //实例化委托,委托Operate的ArraySort排序
            delegateSort = new DelegateSort(Operate.ArraySort);
            //传递参数,调用委托进行排序
            delegateSort(arr);

            Console.WriteLine("排序后");
            foreach (int i in arr)
            {
                Console.WriteLine(i);
            }

            Console.ReadKey();
        }    
    }
}

一切皆地址,程序的本质是数据+算法。

  • 变量(数据)是以某个地址为起点的一段内存中所存储的值
  • 函数(算法)是以某个地址为起点的一段内存中所存储的一组机器语言指令

函数的直接调用和间接调用

  • 直接调用
    通过函数名来调用函数,CPU通过函数名直接获得函数所在地址并开始执行并返回。
  • 间接调用
    通过函数指针来调用函数,CPU通过读取函数指针存储的值来获取函数所在地址并开始执行并返回。

Java中没有与委托相对应的功能实体

预定义委托类型

  • Action 委托
  • Func 委托

除了自定义的委托之外,系统给我们提供了内置的委托类型ActionFunc

Action委托是系统内置预定义的,无返回值的泛型类型。其方法可通泛型来实现。

using System;

namespace CSharp
{
    class Program
    {       
        //无参数且无返回值的方法
        static void Print()
        {
            Console.WriteLine("hello");
        }
        //入口主函数
        static void Main(string[] args)
        {
            //Action是系统预定义的一个委托类型
            //Action指向无参数且无返回值的方法
            Action action = Print;
            action();

            Console.ReadKey();
        }
    }
}

Action委托引用了一个返回值为void的方法,其中T表示方法参数。

Action
Action<in T>
Action<in T1, in T2>
Action<in T1, in T2... in T6>
using System;

namespace CSharp
{
    class Program
    {       
        //无参数且无返回值的方法
        static void Print()
        {
            Console.WriteLine("hello");
        }
        //有参数无返回值的方法
        static void Print(string message)
        {
            Console.WriteLine(message);
        }
        static void Print(int x, int y)
        {
            Console.WriteLine(x+y);
        }
        //入口主函数
        static void Main(string[] args)
        {
            //Action是系统预定义的一个委托类型,默认指向无参数且无返回值的方法。
            Action action = Print;
            action();//hello

            //泛型委托:定义委托类型,此类型可以指向一个没有返回值,但带有一个参数的方法。
            Action<string> act = Print;
            act("world");//world

            //Action可通过泛型T去指定 Action指向的方法的多个参数的类型,参数类型跟action后面声明的委托类型需保持一致。
            Action<int, int> a = Print;
            a(100, 200);

            Console.ReadKey();
        }
    }
}

Func委托引用了一个带有返回值的方法,可传递0或多达16个参数类型,和一个返回类型。

Func<out TResult>
Func<in T, out TResult>
Func<in T1, in T2... in T16, out TResult>
using System;

namespace Test
{
    class Calculator
    {
        public void Report()
        {
            Console.WriteLine("I have 3 methods");
        }
        public int Add(int x, int y)
        {
            return x + y;
        }
        public int Sub(int x, int y)
        {
            return x - y;
        }
    }
    class Program
    {    
        static void Main(string[] args)
        {
            Calculator calculator = new Calculator();
            //直接调用
            calculator.Report();

            //Action委托类型
            Action action = new Action(calculator.Report);
            //间接调用
            action.Invoke();
            //简写
            action();

            //Function泛型委托
            Func<int, int, int> fn1 = new Func<int, int, int>(calculator.Add);
            Func<int, int, int> fn2 = new Func<int, int, int>(calculator.Sub);

            int x = 100;
            int y = 200;
            int z = 0;

            //z = fn1.Invoke(x, y);
            z = fn1(x, y);
            Console.WriteLine("{0} + {1} = {2}", x, y, z);

            //z = fn2.Invoke(x, y);
            z = fn2(x, y);
            Console.WriteLine("{0} - {1} = {2}", x, y, z);

            Console.ReadKey();
        }
    }
}

委托的声明(自定义委托)

  • 委托是一种类class,类是数据类型,所以委托也是一种数据类型。
Type t = typeof(Action);
Console.WriteLine(t.IsClass);//true
  • 委托的声明方式与一般的类不同,主要是为了照顾可读性和C/C++传统。
  • 注意声明委托的位置,避免写错地方,结果声明成嵌套类型。
  • 委托与所封装的方法必须“类型兼容”
委托与所封装的方法必须类型兼容
using System;

namespace Test
{
    //自定义类
    class Calculator
    {
        public double Add(double x, double y)
        {
            return x + y;
        }
        public double Sub(double x, double y)
        {
            return x - y;
        }
        public double Mul(double x, double y)
        {
            return x * y;
        }
        public double Div(double x, double y)
        {
            return x / y;
        }
    }
    //自定义委托
    public delegate double Calc(double x, double y);

    class Program
    {    
        static void Main(string[] args)
        {
            Calculator calculator = new Calculator();
            //使用委托间接调用
            Calc calc = new Calc(calculator.Add);
            double x = 100.00;
            double y = 200.00;
            double z = 0.00;
            //z = calc.Invoke(x, y);
            //简写
            z = calc(x, y);

            Console.WriteLine(z);

            Console.ReadKey();
        }
    }
}

委托的使用

将方法当作参数传递给另一个方法

  • 模板方法 借用指定的外部方法来产生结果
    相当于填空题,常位于代码中部,委托有返回值。
using System;

namespace Test
{
    //产品
    class Product
    {
        public string Name { get; set; }
    }
    //包装
    class Box
    {
        public Product Product { get; set; }
    }
    //包装工厂
    class WrapFactory
    {
        //使用Func委托 模板方法
        public Box WrapProduct(Func<Product> getProduct)
        {
            Box box = new Box();
            Product product = getProduct.Invoke();
            box.Product = product;
            return box;
        }
    }
    //生产工厂
    class ProductFactory
    {
        public Product Make()
        {
            Product product = new Product();
            product.Name = "Pizza";
            return product;
        }
        public Product Build()
        {
            Product product = new Product();
            product.Name = "Car";
            return product;
        }
    }
    class Program
    {    
        static void Main(string[] args)
        {
            ProductFactory productFactory = new ProductFactory();
            WrapFactory wrapFactory = new WrapFactory();

            //使用模板方法
            Func<Product> fn1 = new Func<Product>(productFactory.Make);
            Func<Product> fn2 = new Func<Product>(productFactory.Build);

            Box box1 = wrapFactory.WrapProduct(fn1);
            Box box2 = wrapFactory.WrapProduct(fn2);

            Console.WriteLine(box1.Product.Name);
            Console.WriteLine(box2.Product.Name);

            Console.ReadKey();
        }
    }
}
  • 回调方法callback 调用指定的外部方法
    相当于流水线,常位于代码末尾,委托无返回值。

注意:委托难精通、易使用且功能强大,一旦被滥用后果非常严重。

  • 委托造成方法级别的紧耦合,现实工作中要慎之又慎。
  • 委托造成代码可读性下降,debug难度增加。
  • 将委托回调、异步调用、多线程纠缠在一起,使得代码难以阅读和维护。
  • 委托使用不当有可能造成内存泄漏和程序性能下降。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容