委托
当你需要将方法传递给其他方法时,就可以使用委托。用委托类型形参来调用传递的方法。
声明委托
下面是一个委托声明的例子:
public delegate string DoString(int i)
其中
delegate
是委托的关键字,当你需要声明一个委托时,需要给声明添加delegate关键字。
string
是当前委托的返回值类型。
DoString
是当前委托的委托名(可以理解为类名)
int
是当前委托的参数类型
声明委托可以理解为创建了一个类,委托名对应于类名,所以可以通过New的方法来创建一个委托。
使用委托
委托的类型安全性非常高,使用委托的方法时需要和委托类型有一样的签名(返回值,参数值等),委托才能够成功。 下面是使用委托的例子:
csharp
//声明委托
public delegate string DoString(int i);
//使用委托的方法,签名与委托一致
public static string MyTranslation(int num)
{
Console.WriteLine(num.ToString());
return num.ToString();
}
public static void Main(string[] args)
{
//创建委托,并将委托方法赋值
//可以使用下面两种方式
//第一种通过构造传值
//第二种是委托推断的方法,编译器会自行判断签名是否一致
//委托赋值时绑定的方法不需要加()
DoString doString = new DoString(MyTranslation);
//DoString doString = MyTranslation;
//调用委托方法传入参数值,使用委托的方法同时被调用并接收到传入的参数值
//调用委托方法时需要添加()
doString(87);
}
在控制台输出的结果为
87
Action<T>
和 Func<T>
委托
Action<T>
和 Func<T>
委托是C#中直接定义好的两种委托类型,通常用于给需要将方法做为参数传递的方法,定义参数方法的类型。也可以直接声明Action<T>
和 Func<T>
委托进行委托赋值和使用。
Action<T>
委托
表示一个返回值为void类型,并可接收参数的委托类型。当前委托存在不同变体,可以没有参数值,或最多可以定义8个参数值。 例如:Action<T1,T2,T3> action
则是一个含有三个参数的委托,参数类型分别为T1,T2,T3。
Action<T>
委托还原为委托原型为 public delegate void Action<T>(T t)
Action<T>
委托的使用方法
csharp
//使用Action委托的方法
public static void Add(int num1,int num2)
{
var value = num1+num2;
Console.WriteLine($"{num1}与{num2}相加的值为{value}");
}
//使用Action委托的方法
public static void Sub(int num1,int num2)
{
var value = num1-num2;
Console.WriteLine($"{num1}与{num2}相减的值为{value}");
}
//将Action委托做为方法的参数
public static void Calculate(Action<T1,T2> action,int num1,int num2){
action(num1,num2);
}
public static void Main(string[] args)
{
//声明一个Action委托的数组,将Add和Sub方法加入
Action<int,int>[] actions{
Add,
Sub
}
//遍历数组,调用计算方法,则可以得出不同计算情况下的值
foreach(var action in actions){
Calculation(action,5,8);
}
}
在控制台输出的结果为
5与8相加的值为13
5与8相减的值为-3
Func<in T,out TResult>
委托
表示有一个返回值,并可以接收参数的委托类型。当前委托存在变体,可以没有参数值,或最多可以定义16个参数值,但使返回值类型需要放在泛型参数的最后。
例如Func<in T1,in T2,in T3,out TResult>
则是一个拥有三个参数T1,T2,T3和一个返回值TResult的委托类型。in代表参数,out代表返回值,如果委托只有参数,可以省略in。
Func<in T1,out TResult>
还原为委托原型为 public delegate TResult Func<in T,out TResult>(T t)
Func<in T1,out TResult>
委托的使用方法,对前面Action方法进行修改,可比较差异
csharp
//使用Func委托的方法
public static int Add(int num1,int num2)
{
return num1+num2;
}
//使用Func委托的方法
public static int Sub(int num1,int num2)
{
return num1-num2;
}
//将Func委托做为方法的参数
public static void Calculate(Func<T1,T2> func,int num1,int num2){
var value = func(num1,num2);
Console.WriteLine($"{num1}与{num2} {func.name}的值为{value}");
}
public static void Main(string[] args)
{
//声明一个Func委托的数组,将Add和Sub方法加入
Func<int,int>[] funcs{
Add,
Sub
}
//遍历数组,调用计算方法,则可以得出不同计算情况下的值
foreach(var func in funcs){
Calculation(func,5,8);
}
}
在控制台输出的结果为
5与8 Add的值为13
5与8 Sub的值为-3
多播委托
委托可以包含多个方法,这种委托方式称为多播委托。多播委托可以识别运算符"+"和"+="进行多播委托的绑定,也可以识别"-"和"-="进行多播委托的解绑。
多播委托调用时,绑定方法执行的顺序不是固定的,所以如果希望自行控制委托方法的调用,可以获取委托中绑定的所有方法的数组,并遍历数组进行调用。如果多播委托中的其中一个方法有异常,当抛出异常时,后续方法将不会调用。
csharp
public static void One()
{
Console.WriteLine("我是方法一");
}
public static void Two()
{
Console.WriteLine("我是方法二");
throw new Exception("方法二的异常");
}
public static void Three()
{
Console.WriteLine("我是方法三");
}
public static void Main(string[] args)
{
//第一个绑定方法需要用"="赋值
Action action = One;
action += Two;
action += Three;
//获取action委托绑定的所有方法的数组
Delegate[] delegates = action.GetInvocationList();
foreach (Delegate myDelegate in delegates)
{
try
{
myDelegate();
}
catch (Exception)
{
//捕获异常,并打印。如果没有进行异常捕获,后续方法不会被调用
Console.WriteLine("Catch Execption");
}
}
action -= Two;
action();
}
在控制台输出的结果为
我是方法一
我是方法二
Catch Execption
我是方法三
我是方法一
我是方法三
匿名方法和lambda表达式
匿名方法
匿名方法其实就是定义一个临时方法,不为当前方法署名。优点是减少代码量,但是如果匿名方法多次编写同一种功能代码相同,还是定义一个方法相对合适。且进行委托绑定时,会自动推断出匿名方法的签名与委托是否一致。
csharp
public static void Main(string[] args)
{
//匿名方法与func签名一致
//有参数
Func<int, string> func = delegate(int num)
{
string str = num.ToString();
Console.WriteLine($"{str}在匿名方法中被打印");
//有返回
return str;
};
string str = func(1);
Console.WriteLine($"{str}在Main方法中被打印");
}
在控制台输出的结果为
1在匿名方法中被打印
1在Main方法中被打印
lambda表达式
从C# 3.0版本开始引入了Lambda表达式,提供了与匿名方法相同的功能及其他更多的功能,所以可以直接使用Lambda表达式替代匿名方法。
csharp
public static void Main(string[] args)
{
//num表示参数值
Func<int, string> func = num =>
{
string str = num.ToString();
Console.WriteLine($"{str}在Lambda表达式中被打印");
//有返回
return str;
};
string str = func(1);
Console.WriteLine($"{str}在Main方法中被打印");
}
在控制台输出的结果为
1在Lambda表达式中被打印
1在Main方法中被打印
Lambda表达式的左边列出需要的参数,而其右边定义了赋予Lambda变量的方法的实现代码。 Func<int, string> func = ()=> ConsoleWriteLine("测试")
没有参数可以使用空心括号表示参数。
Func<int, string> func = (num1,num2)=> num1+num2
如果实现代码只有一行可以省略return和花括号,但是有多行一定要添加return和花括号。
闭包
通过Lambda表达式可以访问Lambda块外部的变量,称为闭包。这是Lambda表达式中一个很重要的概念。当访问外部变量时,编译器会给Lambda实现代码创建一个匿名类,在匿名类中创建一个外部变量类型的引用。
ini
int value=5;
Func <int,int> func = x=>x + value;
value=7;
Console.WriteLine(func(5));
在控制台输出的结果为 12
因为实际上编译器为当前Lambda函数创建了匿名类,AnonymousClass和AnonymousMethod并不是匿名类的真实类名和方法名,实际类名和方法名只有编译器知道。
当委托方法被调用时,外部的变量才会通过构造函数进行赋值。
arduino
public class AnonymousClass
{
private int value;
public AnonymousClass(int value){
this.value=value;
}
public int AnonymousMethod(int x)=>x + value;
}
事件
事件是基于委托提供的一种发布订阅机制。在Unity中按钮点击触发都是使用事件的机制。
定义事件的语法为 public event EventHandler<EventArgs> myEvent
event关键字表明当前声明的是一个事件类型的委托,且委托类型是EventHandler
。
事件中的参数有两个,第一个是调用事件的对象,也就是事件本身,为Object
类型;第二个则是事件参数,但这个参数一定要继承EventArgs
类。泛型中只需要声明参数类型。
事件还原为的委托原型为public delegate void EventHandler<EventArgs>(object sender,TEventArgs e) where TEventArgs : EventArgs
事件的添加和删除也是和多播委托一样使用"+="进行添加,"-="进行删除。
事件的使用
csharp
public static void Main(string[] args)
{
var finance = new Finance();
var hr = new HR();
var programmer = new Programmer();
finance.salaryEvent += hr.GetFinanceMessage;
finance.salaryEvent += programmer.GetFinanceMessage;
finance.Notify();
}
public class HR
{
//用于绑定事件的方法
public void GetFinanceMessage(object sender, SalaryEventArgs e)
{
Console.WriteLine($"HR 接收到 Finance 发送的消息 {e.message}");
}
}
public class Programmer
{
//用于绑定事件的方法
public void GetFinanceMessage(object sender, SalaryEventArgs e)
{
Console.WriteLine($"Programmer 接收到 Finance 发送的消息 {e.message}");
}
}
public class Finance
{
//定义一个事件,参数类型是SalaryEventArgs
public event EventHandler<SalaryEventArgs> salaryEvent;
//调用事件的方法
public void Notify()
{
var salaryEventArgs = new SalaryEventArgs();
//?.是对当前对象引用判空操作,非空则进行方法调用
salaryEvent?.Invoke(this, salaryEventArgs);
}
}
//事件参数类型,继承EventArgs
public class SalaryEventArgs : EventArgs
{
public string message = "今天下午发工资";
}
在控制台输出的结果为
HR 接收到 Finance 发送的消息 今天下午发工资
Programmer 接收到 Finance 发送的消息 今天下午发工资
总结
在设计大型应用程序时,使用委托和事件可以减少依赖性和各层的耦合,并能开发出具有更高重用性的组件。