「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:上面的代码有两处简写:
Transformer mysquare = Square;
,完整的写法是这样的:Transformer mysquare = new Transformer(Square);
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
数组排序用不着这么麻烦,但对于相对复杂的class
和struct
来说,我们就可以通过委托来制定相对复杂的比较规则。
委托多播
我个人理解的委托多播是指,委托可以把多个方法组合起来。使用方法如下:
SomeDelegate delegateName = Method1;
delegateName += Method2;
delegateName += Method3;
这样,当我们调用delegateName
的时候,程序就会依此执行Method1, Method2, Method3
。
同样,委托也支持-=
操作。比如,我们执行delegateName -= Method2
,这样,显然delegateName
中还有Method1
和Method3
。这个时候,我们再调用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.Delegate
的Combine
和Remove
两个方法。