「C#学习笔记」委托(上)

委托我是看B站杨旭大佬(B站ID:软件工艺师)的视频学的。不过视频给的例子不多,讲解也比较硬核,所以我编了一些例子来加深理解。

蒟蒻我也是初学,所以如果有错误的地方,还请不吝指正。

代码都是在本地跑过一遍的,应该没有大问题。

这一篇中凡是塞到委托里的方法,都是静态方法。实例方法(可能)会在下一章里讲(如果有的话∠( ᐛ 」∠)_)。

委托是C#中的一个类,很像C/C++中的函数指针。

定义委托

public delegate int Transformer(int x);

delegate是委托的意思。

第一个int表示,Transformer类的委托,其返回值必须是int

后面括号里的int x表示,Transformer类的委托,其传入变量是一个int

举个例子

代码如下:

using System;

namespace LearnDemo
{
    public delegate int Transformer(int x);

    class Program
    {
        static int Square(int u)
        {
            return u * u;
        }

        static void Main(string[] args)
        {
            Transformer mysquare = Square;

            string input = Console.ReadLine();
            int sideLength = Convert.ToInt32(input);

            Console.WriteLine("The area of a square is {0}.", mysquare(sideLength));
        }
    }
}

这里首先定义了一个委托,名为Transformer。根据它的定义,Transformer类接受的方法返回值必须是一个int,而且传入一个int

我们首先在Program类里定义了一个符合上述条件的静态方法——int Square(int u)。然后,我们在Main中定义了一个委托mysquare,并将方法Square传入。

然后,我们就可以在后面直接调用mysquare(参数)来调用Square方法。

上面这个例子只是简单的说明了委托的用法。当然按照上面的场景,我们完全不需要拐弯抹角地用委托,而在Main中直接调用Square就可以了。

PS:上面的代码有两处简写:

  1. Transformer mysquare = Square;,完整的写法是这样的:Transformer mysquare = new Transformer(Square);
  2. mysquare(sideLength);,完整的写法是这样的:mysquare.Invoke(sideLength);

开头说过委托很像C/C++的函数指针。所以,委托的作用之一就是可以作为参数去传递。

作为参数传递

还是看个例子,给数组排序:

using System;
using System.Collections.Generic;
using System.Linq;

namespace LearnDemo
{
    // 类型名啥的都是乱起的,不要在意
    public delegate bool Compare<T>(T value1, T value2);

    class Program
    {
        // 就是个简单的插入排序,不用在意细节
        private static void MySort<T>(T[] list, Compare<T> cmp)
        {
            for (int i = 0; i < list.Length; i++)
            {
                int bigcur = i;
                for (int j = i; j < list.Length; j++)
                {
                    if (cmp(list[j], list[bigcur])) bigcur = j;
                }

                T temp = list[i];
                list[i] = list[bigcur];
                list[bigcur] = temp;
            }
        }

        // 就是个简单的比较大小
        private static bool cmpint(int a, int b)
        {
            if (a < b) return true;
            else return false;
        }

        static void Main(string[] args)
        {
            string input;

            // 输入一行数字,用空格分开
            input = Console.ReadLine();
            string[] strarr = input.Split(' ');

            // 把这一行数塞到int数组里
            int[] a = new int[strarr.Length];
            for (int i = 0; i < strarr.Length; i++)
                a[i] = Convert.ToInt32(strarr[i]);

            // 定义委托
            Compare<int> cmp = cmpint;
            // 这里通过向MySort传递委托cmp来传递数组a排序的比较规则
            // 虽然对于int来说完全不必这么做233333333
            MySort(a, cmp);

            // 输出
            foreach (var i in a)
                Console.Write(i + " ");
        }
    }
}

如果你熟悉C++,那么这个用法你肯定不陌生。这里和C++向sort函数里传递比较函数的用法完全一样(只不过我懒得写快速排序所以就写了插入排序哈哈哈哈)。

当然,给int数组排序用不着这么麻烦,但对于相对复杂的classstruct来说,我们就可以通过委托来制定相对复杂的比较规则。

委托多播

我个人理解的委托多播是指,委托可以把多个方法组合起来。使用方法如下:

SomeDelegate delegateName = Method1;
delegateName += Method2;
delegateName += Method3;

这样,当我们调用delegateName的时候,程序就会依此执行Method1, Method2, Method3

同样,委托也支持-=操作。比如,我们执行delegateName -= Method2,这样,显然delegateName中还有Method1Method3。这个时候,我们再调用delegateName的时候,它就会依次执行Method1, Method3

如果delegateName只剩下Method1了,如果我们再执行delegateName -= Method1,那么delegateName就会变成null。此时不能直接调用这个委托,否则会出错。

PS:这里提一句,我们执行+=-=的时候,程序实际上是重新创建了一个委托,然后在原来委托的基础上加上(去掉)新的委托,然后销毁原来的委托。

举个复杂一点的例子

比如,我们输入若干学生的ID和成绩,要求我们按照ID顺序输出每个学生的名次,我们就可以把按照成绩排序、给学生排名、按照ID排序塞到一个委托多播里。这样,我们就可以直接调用这一个委托来全部执行这些操作。(其实本人也是初学,实在想不到更好的例子了哈哈哈)

using System;

namespace LearnDemo
{
    // Student的定义。因为一共就3个int所以就定义成struct了
    struct Student
    {
        public int id, score, rank;
    }

    class Program
    {
        // 为了方便没写成泛型,而且只是为了举多播的例子也没必要
        private delegate void StudentOperation(Student[] list);

        // Swap是为了排序写的,可以忽略
        static private void Swap<T>(ref T a, ref T b)
        {
            T temp = a;
            a = b;
            b = temp;
        }

        // 按照成绩排序,细节可以忽略
        static void SortByScore(Student[] students)
        {
            for (int i = 0; i < students.Length; i++)
            {
                int cur = i;
                for (int j = i + 1; j < students.Length; j++)
                {
                    if (students[j].score > students[cur].score)
                        cur = j;
                }

                Swap(ref students[i], ref students[cur]);
            }
        }

        // 赋予排名,细节可以忽略
        static void AssignRank(Student[] students)
        {
            for (int i = 0; i < students.Length; i++)
                students[i].rank = i + 1;
        }

        // 按照Id排序,细节可以忽略
        static void SortById(Student[] students)
        {
            for (int i = 0; i < students.Length; i++)
            {
                int cur = i;
                for (int j = i + 1; j < students.Length; j++)
                {
                    if (students[j].id < students[cur].id)
                        cur = j;
                }

                Swap(ref students[i], ref students[cur]);
            }
        }

        static void Main(string[] args)
        {
            // 输入的引导和提示,可以忽略
            Console.WriteLine("Please input the number of students:");
            int num = int.Parse(Console.ReadLine());

            Console.WriteLine("Then input {0} pairs of student informations, " +
                "each pair contains student id and score, seperate them by one space please.", num);
            Student[] students = new Student[num];

            // 把字符串转换成数字,可以忽略
            for (int i = 0; i < num; i++)
            {
                string[] str2 = Console.ReadLine().Split(' ');
                students[i].id = int.Parse(str2[0]);
                students[i].score = int.Parse(str2[1]);
            }

            // 创建委托多播
            StudentOperation operation = new StudentOperation(SortByScore);
            operation = operation + AssignRank + SortById;

            // 调用委托多播
            operation(students);

            // 输出
            Console.WriteLine("Rank List:");
            foreach (var i in students)
            {
                Console.WriteLine("ID: {0}, score: {1}, rank: {2}", i.id, i.score, i.rank);
            }
        }
    }
}

至少,我们可以复用operation(实在想不到更好的原因了,就写一写练练手吧2333333)。

关于委托多播的一些坑?

PS:虽然说,委托多播是把一系列的方法结合了起来,但是它只返回最后一个函数的返回值。前面的方法会被调用,但是其返回值就直接被弃用了。

C#会把委托的+, +=, -, -=编译成System.DelegateCombineRemove两个方法。

posted @ 2020-11-12 19:47  icysky  阅读(191)  评论(0编辑  收藏  举报