C#委托详细理解

可以理解为 把方法当成方法的参数

public void HelloWorld(string name, 方法 方法名)

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中的ToSSString        ()上,这样method就相当于x.ToString
        //ToString方法用来把数据转换成字符串
        //GetString method = new GetString(x.ToString);
        
        //初始化委托:直接将方法赋值为委托类型的变量
        GetString methed = 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();
        }    
    }
}

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

(1)变量(数据)是以某个地址为起点的一段内存中所存储的值

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

函数的直接调用间接调用

直接调用:
通过函数名来调用函数,CPU通过函数名直接获得函数所在地址并开始执行并返回。

间接调用:

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

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

预定义委托类型

(1)Action 委托

(2)Func 委托

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

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();
        }
    }
}

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

(1)委托是一种类class,类是数据类型,所以委托也是一种数据类型

Type t = typeof(Action);
Console.WriteLine(t.IsClass);//true

(2)委托的声明方式与一般的类不同,主要是为了照顾可读性和C/C++传统

(3)注意声明委托的位置,避免写错地方,结果声明成嵌套类型

(4)委托与所封装的方法必须“类型兼容”

delegate double Calc[double x,double y];
        double Add[double x, double y]{return x + y;}
        double Sub[double x, double y]{return x - y;}
        double Mul[double x, double y]{return x * y;}
        double Div[double x, double y]{return x / y;}

(1)返回值的数据类型一致
(2)参数列表在个数和数据类型上一致(参数名不需要一样)

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();
        }
    }
}

委托的使用
将方法当作参数传递给另一个方法
(1)模板方法 借用指定的外部方法来产生结果
相当于填空题,常位于代码中部,委托有返回值。

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();
        }
    }
}

(2)回调方法callback 调用指定的外部方法相当于流水线,常位于代码末尾,委托无返回值。
(3)注意:委托难精通、易使用且功能强大,一旦被滥用后果非常严重。

  • 委托造成方法级别的紧耦合,现实工作中要慎之又慎。
  • 委托造成代码可读性下降,debug难度增加。
  • 将委托回调、异步调用、多线程纠缠在一起,使得代码难以阅读和维护。
  • 委托使用不当有可能造成内存泄漏和程序性能下降。
发布了50 篇原创文章 · 获赞 8 · 访问量 4986
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章