携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情 >>
前言
今天带来的是委托相关技术博客,此文是之前学习C#委托的时候的笔记,今天正好复习一下。
委托是什么
委托是定义方法签名的引用类型数据类型。 您可以定义委托的变量,就像其他数据类型一样,可以引用与委托具有相同签名的任何方法。
我们使用委托时会涉及到三个步骤:
- 声明一个委托 Delegate
- 设置目标方法
- 调用委托 Delegate
委托定义
- 声明委托:
[访问修饰符] delegate [返回类型] [委托名称]([参数...])
例如publicdelegatevoidMyDelegate(string msg);
- 声明委托后,我们需要设置目标方法或 lambda 表达式。 我们可以通过使用 new 关键字创建委托对象并传递签名与委托签名匹配的方法来实现。再进行调用委托
using System;
namespace ConsoleApp1
{
public delegate void MyDelegate(string msg); // 声明一个委托
class Program
{
// 跟MyDelegate有相同参数和返回值的方法
static void MethodA(string message)
{
Console.WriteLine(message);
}
static void Main(string[] args)
{
// 把函数传给委托
MyDelegate del1 = new MyDelegate(MethodA);
// 直接赋值给委托
MyDelegate del2 = MethodA;
// 使用 lambda表达式
MyDelegate del3 = (string msg) => Console.WriteLine(msg);
del1.Invoke("1");
del2("2");
del3("3");
Console.ReadKey();
}
}
}
- 委托也可以当作一个参数进行传递
ex:
using System;
namespace ConsoleApp1
{
public delegate void MyDelegate(string msg); // 声明一个委托
class Program
{
static void Main(string[] args)
{
MyDelegate del = ClassA.MethodA;
InvokeDelegate(del);
del = ClassB.MethodB;
InvokeDelegate(del);
del = (string msg) => Console.WriteLine("调用 lambda 表达式: " + msg);
InvokeDelegate(del);
}
static void InvokeDelegate(MyDelegate del) // 参数是一个委托
{
del("Hello World");
}
}
public class ClassA
{
public static void MethodA(string message)
{
Console.WriteLine("调用 ClassA.MethodA(): " + message);
}
}
public class ClassB
{
public static void MethodB(string message)
{
Console.WriteLine("调用 ClassB.MethodB(): " + message);
}
}
}
多播委托
- 一个委托实例可以引用一组目标方法,所有的委托实例都具有多播的功能。
- 使用+或+=操作符就可以合并委托实例,合并后调用就会顺序地调用各个委托实例委托的方法,调用的顺序跟合并的顺序一致。
- 使用-或-=操作访可以移除合并的委托实例。
- C#会把这些操作符编译成System.Delegate中的Combine和Remove两个静态方法
class Program
{
public delegate int Transformer(int x);
static void Main(string[] args)
{
Transformer t = null;
//在这里方法也可以当成是一个委托实例
//Transformer t = Square
t += Square;
//t = t + Cube
t += Cube;
//调用的时候就会按照Square、Cube顺序进行调用
//返回最后一个方法的返回值
var i = t(3);
Console.WriteLine(i);
//移除委托实例
//若把所有的委托实例都移除了,那么委托实例会为null
t -= Cube;
var j = t(3);
Console.WriteLine(j);
}
static int Square(int x)
{
var result = x * x;
Console.WriteLine(result);
return result;
}
static int Cube(int x)
{
var result = x * x * x;
Console.WriteLine(result);
return result;
}
}
泛型委托
- 委托类型可以包含泛型类型的参数,public delegate T Transformer (T arg);
Func和Action委托
Func
- Func类型的定义都放在了System命名空间下,它代表的是有返回值的委托类型,它有多种不同的泛型类型可供使用:
-
- delegate TResult Func();:有返回值,没有参数的委托类型,out表示TResult类型的变量只能作为方法的输出。
- delegate TResult Func<in T, out TResult>(T arg);:有返回值,一个参数的委托类型,in表示T类型的变量只能作为方法的输入。
- delegate TResult Func<in T1, in T2... out TResult>(T1 arg1, T2 arg2...);:有返回值,多个个参数的委托类型,最多可以支持16个输入参数。
Action
- Action类型的定义都放在了System命名空间下,它代表的是没有返回值的委托类型,它有多种不同的泛型类型可供使用:
- delegate void Action();:没有返回值,没有参数的委托类型。
- delegate void Action(T arg);:没有返回值,一个参数的委托类型。
- delegate void Action<in T1, in T2...>(T1 arg1, T2 arg2...);:没有返回值,多个个参数的委托类型,最多可以支持16个输入参数。
class Program
{
static void Main(string[] args)
{
int[] values1 = {1, 2, 3};
//使用Func委托类型进行委托
Transform1(values1, Square);
foreach (var value in values1)
{
Console.WriteLine(value);
}
//使用Action委托类型进行委托
int[] values2 = { 1, 2, 3 };
Transform2(values2, Cube);
}
static void Transform1<T>(T[] values, Func<T, T> tranFunc)
{
for (int i = 0; i < values.Length; i++)
{
//Func有返回值
values[i] = tranFunc(values[i]);
}
}
static void Transform2<T>(T[] values, Action<T> tranAction)
{
for (int i = 0; i < values.Length; i++)
{
//Action没有返回值
tranAction(values[i]);
}
}
static int Square(int x) => x * x;
static void Cube(int x) => Console.WriteLine(x*x*x);
}
\
断言委托 Predicate
Predicate 是类似于 Func 和 Action 委托的委托。 它表示一个包含一组标准的方法,并检查传递的参数是否满足这些标准。 Predicate委托方法必须接受一个输入参数并返回一个布尔值 - 真或假。
Predicate 委托在 System 命名空间中定义,如下所示:
public delegate bool Predicate<in T>(T obj);
与其他委托类型一样,Predicate 也可以与任何方法、匿名方法或 lambda 表达式一起使用。
class Program
{
static bool IsUpperCase(string str)
{
return str.Equals(str.ToUpper());
}
static void Main(string[] args)
{
Predicate<string> isUpper = IsUpperCase;
bool result = isUpper("hello world!!");
Console.WriteLine(result); //False
}
}
接口和委托
- 委托可以解决的委托,接口都可以解决,那在什么时候更适合自定义的委托而不是使用接口呢?
-
- 需要多播的功能,接口不能实现多播的功能
- 订阅者需要多次实现接口
委托的兼容性
- 委托类型:委托类型之间时互不相容的,即使方法签名一样。
- 委托实例:如果委托实例拥有相同的方法目标,那么委托实例就是相等的。
- 参数:委托可以接受比它的定义参数类型更加抽象的参数类型的方法,这个叫做委托的逆变。
- 返回值:委托可以接受比它的定义返回类型更加具体的返回类型的方法,这个叫做委托的协变。
lambda表达式
示例lambda
ex:使用匿名方法检查学生是否是青少年
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
static void Main(string[] args)
{
Predicate<Student> predicate = delegate (Student s) { return s.Age > 12 && s.Age < 20; };
}
上述匿名方法可以使用 C# Lambda 表达式表示如下:
class Program
{
static void Main(string[] args)
{
Predicate<Student> predicate = s => s.Age > 12 && s.Age < 20;
}
}
lambda的演化过程
让我们看看 lambda 表达式是如何从以下匿名方法演变而来的
匿名方法
delegate (Student s) { return s.Age > 12 && s.Age < 20; };
Lambda表达式
static void Main(string[] args)
{
Predicate<Student> predicate = delegate (Student s) { return s.Age > 12 && s.Age < 20; };
// 把 delegate 去掉了 加入了 => 关键的一步。换了一种表达方式而已
Predicate<Student> predicate2 = (Student s) => { return s.Age > 12 && s.Age < 20; };
// 把类型去掉了。因为是可以推断出来的。
Predicate<Student> predicate3 = (s) => { return s.Age > 12 && s.Age < 20; };
//因为只有一个语句,所以 return 和大括号也可以被省略
Predicate<Student> predicate4 = (s) => s.Age > 12 && s.Age < 20;
// 因为只有一个参数所以 括号也可以补省略
Predicate<Student> predicate5 = s => s.Age > 12 && s.Age < 20;
}
lambda多个参数
//类型参数可以省是因为左边有定义了,所以推断得出来
Func<Student, int, bool> func = (s, minAge) => { return s.Age > minAge && s.Age < 20; };
lambda 无参
Action action = () => Console.WriteLine("hello world");
lambda表达式多个语句
Predicate<Student> predicate6 = (s) =>
{
s.Age += 10;
return s.Age > 12 && s.Age < 20;
};
lambda 表达式里定义变量
Predicate<Student> predicate7 = (s) =>
{
var maxAge = 20;
return s.Age > 12 && s.Age < maxAge;
};
\
lambda 表达式使用外部的变量
var maxAge = 20;
Predicate<Student> predicate8 = (s) =>
{
return s.Age > 12 && s.Age < maxAge;
};