Keep on going never give up.

Let's Go

C# 学习笔记(50)委托

C#Lonely2019-05-25 23:22:52116次0条

C#中的委托可以理解为函数的包装,它使得C#中的函数可以被作为参数来被传递。这种将函数动态地赋给参数的做法,可以避免在程序中大量使用if-else(Switch)语句,同时使得程序具有更好的可扩展性。用最通俗易懂的话来讲,你就可以把委托看成是用来执行方法(函数)的一个东西。在使用委托的时候,你可以像对待一个类一样对待它。即先声明,再实例化。只是有点不同,类在实例化之后叫对象或实例,但委托在实例化后仍叫委托。

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

可以被委托包装的方法必须满足以下规则:

1、方法的签名必须与委托一致,方法签名包括参数的个数、类型和顺序。

2、方法的返回类型要和委托一致,注意,方法的返回类型不属于方法签名的一部分。

委托使得一个方法可以作为另一个方法的参数进行传递,这就是委托最大的作用。

代码示例:

using System;

namespace ConsoleApp
{
    public delegate void Greeting(string name); //定义委托类型

    class Program
    {
        static void Main(string[] args)
        {
            //声明委托变量,并实例化委托类型
            Greeting greeting_1 = new Greeting(ChineseGreeting);
            //上面这句,可以简写为下面的形式,其本质上还是new Greeting,只不过这部分编译器帮我们自动完成了
            Greeting greeting_2 = EnglishGreeting;

            //调用委托
            greeting_1("林莉"); //隐式调用委托,其背后仍通过调用Invoke方法来实现的,只不过这一步由编译器来帮我们完成了。
            greeting_2.Invoke("沈婷婷"); //显式调用委托

            //匿名函数
            Greeting greeting_3 = delegate (string name) {
                Console.WriteLine("你好"+name+ ",我是吴亦凡,你看这灯,多亮。");
            };
            greeting_3("李雪琴");

            // lamda表达式,其本质也是匿名函数  => “goes to”
            Greeting greeting_4 = (string name) => { Console.WriteLine("你好"+name+ ",鸡你真美。"); };
            greeting_4("蔡徐坤");
         
            Console.ReadKey();
        }

        //英文问候
        public static void EnglishGreeting(string name)
        {
            Console.WriteLine("Hello,"+name);
        }

        //中文问候
        public static void ChineseGreeting(string name)
        {
            Console.WriteLine("你好," + name);
        }

    }
}

运行结果:

image.png


委托的本质

委托是类类型,委托的本质就是一个类。通过上面的代码,反编译查看其生成的IL代码,如下图所示:

image.png

由上图可知,委托类型使用.class关键字进行标识,实际上定义任何委托编译器都会做如下工作:

1、声明一个类,对应上图中的.class nested public auto ansi sealed。

2、该类扩展自System.MulticastDelegate,对应上图中的extends [mscorlib]System.MulticastDelegate。

3、该类包含一个构造器,对应上图中的.ctor: void(object ,native int)。

4、该类包含三个方法,分别是BeginInvoke、EndInvoke、Invoke。

任何委托都继承自[mscorlib]System.MulticastDelegate类型,该类包含了一个构造函数和3个方法,有了构造函数,我们才能使用new关键字来实例化委托类型。而Inoke方法则用来显式地调用委托,此外IAsyncResult和EndInvoke方法是两个异步方法。


多播委托

C#中把封装多个方法的委托称作为委托链多路广播委托

委托链其实就是委托类型,只是委托链把多个委托链接在一起而已,也就是说,我们把链接了多个方法的委托称为委托链或多路广播委托。

代码示例:

using System;

namespace ConsoleApp
{
    public delegate void Greeting(string name); //定义委托类型

    class Program
    {
        static void Main(string[] args)
        {           
            Greeting greeting = ChineseGreeting;
            greeting += EnglishGreeting; //使用“+”符号链接委托,链接多个委托后就成为了委托链
            greeting("唐丽丽");

            Console.ReadKey();
        }

        //英文问候
        public static void EnglishGreeting(string name)
        {
            Console.WriteLine("Hello,"+name);
        }

        //中文问候
        public static void ChineseGreeting(string name)
        {
            Console.WriteLine("你好," + name);
        }

    }
}

运行结果:

image.png

从以上代码可知,通过使用“+”运算符,我们能将多个委托链接到一个委托对象实例上,使其成为多路广播委托。在调用委托链时,被绑定到委托链中的每个委托都会被执行。

从委托链中移除委托:既然能用“+”运算符把委托链接到一个委托对象实例上,自然也可以使用“-”运算符将某个委托从委托链对象上移除。


泛型委托

泛型委托,与普通委托类似,不同之处只在于使用泛型委托要指定泛型参数,为了方便开发,.NET基类库针对在实际开发中最常用的情形提供了几个预定义好的委托,以免我们在自己使用时还繁琐重复的去定义它,它们分别是ActionFuncPredicate,这是我在资料上摘取的这几个委托的区别:

(1)、Action

Action 是无返回值的泛型委托。

Action 表示无参,无返回值的委托

Action<int,string> 表示有传入参数int,string无返回值的委托

Action<int,string,bool> 表示有传入参数int,string,bool无返回值的委托

Action<int,int,int,int> 表示有传入4个int型参数,无返回值的委托

Action至少0个参数,至多16个参数,无返回值。

(2)、Func

Func是有返回值的泛型委托

Func<int> 表示无参,返回值为int的委托

Func<object,string,int> 表示传入参数为object,string 返回值为int的委托

Func<object,string,int> 表示传入参数为object, string 返回值为int的委托

Func<T1,T2,T3,int> 表示传入参数为T1,T2,T3(泛型)返回值为int的委托

Func至少0个参数,至多16个参数,根据返回值泛型返回。必须有返回值,不可void

(3)、Predicate

predicate 是返回bool型的泛型委托

predicate<int> 表示传入参数为int 返回bool的委托

Predicate有且只有一个参数,返回值固定为bool

一般的需求下,我们就使用微软定义的委托就足够了,这样减少了我们对委托的重复定义,可能有部分初学者见到Func<>,Action<>这样的代码肯定会很懵比,这只是你对新东西陌生罢了,多结合实例敲几遍,自然就会用了,它们其实就是微软封装定义好了的委托,没有什么特别的。

代码示例:

using System;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {          
            //Action<T> 用法,比起上面的自定义委托,明显可以看出代码简洁了
            Action<string> greeting = ChineseGreeting;
            greeting("沈月");
            Console.ReadKey();
        }

        //英文问候
        public static void EnglishGreeting(string name)
        {
            Console.WriteLine("Hello,"+name);
        }

        //中文问候
        public static void ChineseGreeting(string name)
        {
            Console.WriteLine("你好," + name);
        }

    }
}

使用泛型委托,求任意类型数组最大值,代码示例:

using System;

namespace ConsoleApp
{
    public delegate int DelCompare<T>(T t1,T t2); //定义泛型委托
    
    class Program
    {
        static void Main(string[] args)
        {
            int[] nums = { 10,20,30,40,50 };
            int max = GetMax<int>(nums, Compare);
            Console.WriteLine("该数组最大值是:"+max);

            string[] arr = { "我将于茫茫人海中访我唯一灵魂之伴侣", "得之", "我幸", "不得", "我命", "徐志摩" };
            string strMaxLength = GetMax<string>(arr, (string s1, string s2) =>
            {
                return s1.Length - s2.Length;
            });
            Console.WriteLine("最大长度的字符串是:"+strMaxLength);
            Console.ReadKey();
        }
       
        public static T GetMax<T>(T[] nums, DelCompare<T> del)
        {
            T max = nums[0];
            for (int i = 0; i < nums.Length; i++)
            {
                //要传一个比较方法
                if (del(max, nums[i]) < 0)
                    max = nums[i];
            }
            return max;
        }

        // int数组比较方法
        public static int Compare(int n1, int n2)
        {
            return n1 - n2;
        }

    }
}

运行结果:

image.png






暗锚,解决锚点偏移

文章评论

    嘿,来试试登录吧!