委托(Delegate)
委托(Delegate)
什么是委托?
使用委托就是可以把方法当做变量使用
在C语言中 函数指针是指向一个函数的地址 函数指针变量可以作为一个参数进行传递 在C#中可以将委托类比成C语言中的函数指针 将可变的操作当做参数传入方法中进行调用
委托声明
声明委托变量之前要声明委托类型 用来指定方法签名(委托返回类型 参数列表)的信息 这里声明了几种不同的委托类型
/// <summary>
/// 参数类型为object的委托类型
/// </summary>
/// <param name="objPara"></param>
public delegate void InputObjDelegate(object objPara);
/// <summary>
/// 参数类型为string的委托类型
/// </summary>
/// <param name="strPara"></param>
public delegate void InputStrDelegate(string strPara);
/// <summary>
/// 参数类型为int的委托类型
/// </summary>
/// <param name="intPara"></param>
public delegate void InputIntDelegate(int intPara);
/// <summary>
/// 返回类型为object的委托类型
/// </summary>
/// <returns></returns>
public delegate object OutputObjDelegate();
/// <summary>
/// 返回类型为int的委托类型
/// </summary>
/// <returns></returns>
public delegate int OutputIntDelegate();
定义一个类用于展示委托的使用 在类中提供了几个实例方法与静态方法用于委托实例化 (MyPrint类)
public class MyPrint
{
/// <summary>
/// 打印object静态方法
/// </summary>
/// <param name="objPara"></param>
public static void StaticPrintObject(object objPara)
{
Console.WriteLine("打印object静态方法:" + objPara);
}
/// <summary>
/// 打印string静态方法
/// </summary>
/// <param name="strPara"></param>
public static void StaticPrintStr(string strPara)
{
Console.WriteLine("打印string静态方法:" + strPara);
}
/// <summary>
/// 返回类型为string静态方法
/// </summary>
/// <returns></returns>
public static string StaticReturnStr()
{
Console.WriteLine("返回string静态方法");
return "返回string静态方法";
}
/// <summary>
/// 返回类型为Int静态方法
/// </summary>
/// <returns></returns>
public static int StaticReturnInt()
{
Console.WriteLine("返回Int静态方法");
return 0;
}
/// <summary>
/// 打印string方法
/// </summary>
/// <param name="strPara"></param>
public void PrintStr(string strPara)
{
Console.WriteLine("打印string方法:" + strPara);
}
/// <summary>
/// 返回类型为string方法
/// </summary>
/// <returns></returns>
public string ReturnStr()
{
Console.WriteLine("返回string静态方法");
return "返回string静态方法";
}
}
委托的实列化
C#1.0版本委托的实例化与类一样 使用new关键字 构造的时候需要传参数类型数完全相同的方法 非静态方法需要首先实例化对象 使用实例化对象传入方法 静态方法需要使用类名传入方法
MyPrint myPrint = new MyPrint();
InputStrDelegate delInStr = new InputStrDelegate(myPrint.PrintStr);//非静态方法实例化
delInStr = new InputStrDelegate(MyPrint.StaticPrintStr);//静态方法实例化
C#2.0版本提供了匿名方法 方法组转换 可以使用这两种语法进行委托实例化
//匿名方法实例化
delInStr = delegate (string strPara)
{
return;
};
delInStr = MyPrint.StaticPrintStr;//方法组转换实例化
使用省略参数的匿名方法实例化委托时 这种省略参数的匿名方法可以转换为具有任何参数列表的委托类型
C#3.0版本使用lambda 表达式进行委托实例化 也不支持参数省略的功能
//lambda表达式实例化
delInStr = (p) =>
{
Console.WriteLine("委托实例化:lambda表达式");
};
委托的调用
委托调用分为同步调用与异步调用 首先实例化一个委托用于委托调用
InputStrDelegate delInStrInvoke = (p) =>
{
Console.WriteLine("委托调用");
};
同步调用
使用委托实例的Invoke方法可以进行委托的同步调用 参数列表与委托相同
delInStrInvoke.Invoke("参数");
C#为我们提供了另一种方式 可以使用更简单的方式进行调用 就像正常调用方法一样
delInStrInvoke("参数");
异步调用
使用委托实例的BeginInvoke方法可以进行委托的异步调用 该方法会使用一个后台线程进行异步的操作 参数列表中除去最后两个之外 与委托参数列表相同 最后两个参数分别传入回调方法 异步状态值(不使用可以传入null)
//AsyncCallback参数传入回调方法(委托)
//object参数传入回调State参数 回调方法中使用IAsyncResult参数类型的AsyncState属性获取
//后台线程
IAsyncResult asyncResult = delInStrInvoke.BeginInvoke("参数",
(p) =>
{
Console.WriteLine(p.AsyncState);
},
"回调State参数");
异步调用状态/结果有以下几种处理方式
while(!asyncResult.IsCompleted) { };//IsCompleted判断操作是否完成
asyncResult.AsyncWaitHandle.WaitOne();//阻塞等待操作完成
delInStrInvoke.EndInvoke(asyncResult);//阻塞等待操作完成 获取返回类型值
委托多播
委托多播允许在一个委托中注册多个方法 在内部维护了一个的委托数组
实例化三个委托
InputStrDelegate delInStr1 = (p) =>
{
Console.WriteLine("委托多播:1" + " 参数:" + p);
};
InputStrDelegate delInStr2 = (p) =>
{
Console.WriteLine("委托多播:2" + " 参数:" + p);
};
InputStrDelegate delInStr3 = (p) =>
{
Console.WriteLine("委托多播:3" + " 参数:" + p);
};
合并委托多播需要使用Delegate类提供的Combine静态方法
InputStrDelegate delInStrMC =
Delegate.Combine(delInStr1, delInStr2) as InputStrDelegate;
delInStrMC = Delegate.Combine(delInStrMC, delInStr3) as InputStrDelegate;
移除委托多播需要使用Delegate类提供的Remove静态方法
delInStrMC = Delegate.Remove(delInStrMC, delInStr2) as InputStrDelegate;
C#同时也提供了运算符重载 + – += -=用来快速的进行委托多播的注册与移除
InputStrDelegate delInStrMC1 = delInStr1 + delInStr2;
delInStrMC1 = delInStrMC1 + delInStr3;
delInStrMC1 = delInStrMC1 - delInStr2;
InputStrDelegate delInStrMC2 = delInStr1;
delInStrMC2 += delInStr2;
delInStrMC2 += delInStr3;
delInStrMC2 -= delInStr2;
委托多播的调用方式与委托调用方式相同(同步、异步) 需要注意的是:
- 委托多播合并的委托会顺序执行
- 有返回类型的委托只会返回最后一个返回类型
- 如果某个委托抛出异常异常 后面的委托不会继续执行
- *谨慎使用匿名方法合并委托多播 考虑移除委托多播情况
- 使用委托带来的闭包问题
委托强类型
C#为了避免每次使用委托都需要声明委托类型 提供了多个强类型的委托类型便于复用
无返回类型的委托类型Action 提供了最多16个泛型参数作为输入列表
Action action = () =>
{
Console.WriteLine("委托强类型:Action");
};
Action<string> actionInStr = (p) =>
{
Console.WriteLine("委托强类型:Action" + " 参数:" + p);
};
有返回类型的委托类型Func 提供了最多16个泛型参数作为输入列表 最后一个泛型指定返回类型类型
Func<int> func = () =>
{
Console.WriteLine("委托强类型:Func");
return 0;
};
Func<string, int> funcInStr = (p) =>
{
Console.WriteLine("委托强类型:Func" + " 参数:" + p);
return 0;
};
除此之外还提供了Predicate(泛型指定参数类型类型 返回bool类型 Comparison(泛型指定两个参数类型共同类型 返回int类型) Converter(泛型指定参数类型 返回类型类型)等委托
//其他委托
Predicate<int> predicate = (p) =>
{
Console.WriteLine("委托强类型:Predicate" + " 参数:" + p);
return p > 0;
};
Comparison<int> comparison = (x, y) =>
{
Console.WriteLine("委托强类型:Comparison" + " 参数x:" + x + " 参数y:" + y);
return x - y;
};
Converter<int, string> converter = (p) =>
{
Console.WriteLine("委托强类型:Converter" + " 参数:" + p);
return p.ToString();
};
委托可变性
C#中可变性分为协变 逆变 更加具体一点的说应该是返回类型的协变性 参数类型的逆变性
返回类型派生程度较大的方法可以用来实例化返回类型派生程度较小的委托 比如声明一个返回类型为object的委托 可以使用返回类型为string的方法来实例化该委托
PS:可以这么理解 当我调用OutputObjDelegate 委托的时候 该委托应该返回一个object类型的参数 那么MyPrint.StaticReturnStr方法返回了一个string类型的参数 string类型可以隐式转换为object 对该委托来说符合它的返回类型
//返回类型协变
OutputObjDelegate delOutObjOut = MyPrint.StaticReturnStr;
参数类型派生程度较小的方法可以用来实例化参数类型派生程度较大的委托 比如声明一个参数类型为string的委托 可以使用参数类型为object的方法来实例化该委托
PS:可以这么理解 当我调用InputStrDelegate委托的时候 应该传入一个string类型的参数 这时候该委托调用了MyPrint.StaticPrintObject方法 传入一个string类型的参数作为该方法的object类型参数 同样string类型可以隐式转换为object 并不会违反委托参数类型的限制
//参数类型逆变
InputStrDelegate delInStrIn = MyPrint.StaticPrintObject;
委托可变性只支持引用类型
PS:C#中引用类型派生自System.Object类 值类型派生自System.ValueType类 System.ValueType类派生自System.Object类 值类型的基类为object 同样是派生关系 但是可变性并不支持值类型 有没有想过这是为什么呢?
委托的本质
委托的本质究竟是什么 来反编译一下看看InputObjDelegate委托的MSIL代码
.class public auto ansi sealed Library.InputObjDelegate
extends [mscorlib]System.MulticastDelegate
很明显得出结论 对于MSIL来说委托其实就是一个类(.class) 并且不可被继承(sealed) 派生自System.MulticastDelegate类(extends [mscorlib]System.MulticastDelegate)
PS:委托类型被声明在类内部则被编译为一个嵌套类型(nested)的类 可以简单的理解为内部类
查看文档:
[MulticastDelegate 类 (System) | Microsoft Docs](.NAT文档)
文档中说明该类提供了委托多播的实现 内部维护了一个调用列表用来处理委托多播 派生自System.Delegate类 且我们不能从该类显示派生
反编译一下委托实例化的代码
// inputStrDelegate = delegate
// {
// Console.WriteLine("委托实例化:lambda表达式");
// };
IL_008e: ldsfld class [Library]Library.InputStrDelegate DelegateConsole.Program/'<>c'::'<>9__0_3'
// (no C# code)
IL_0093: dup
IL_0094: brtrue.s IL_00ad
IL_0096: pop
IL_0097: ldsfld class DelegateConsole.Program/'<>c' DelegateConsole.Program/'<>c'::'<>9'
IL_009c: ldftn instance void DelegateConsole.Program/'<>c'::'<Main>b__0_3'(string)
IL_00a2: newobj instance void [Library]Library.InputStrDelegate::.ctor(object, native int)
IL_00a7: dup
IL_00a8: stsfld class [Library]Library.InputStrDelegate DelegateConsole.Program/'<>c'::'<>9__0_3'
其实只是加载了对应的方法(相当于函数指针) 单独使用委托实例化与使用方法并没有区别
再反编译看一下委托多播(+=)
// InputStrDelegate a = Delegate.Combine(inputStrDelegate3, inputStrDelegate4) as InputStrDelegate;
IL_01a5: ldloc.s 5
IL_01a7: ldloc.s 6
IL_01a9: call class [System.Runtime]System.Delegate [System.Runtime]System
.Delegate::Combine(class [System.Runtime]System.Delegate, class [System.Runtime]System.Delegate)
IL_01ae: isinst [Library]Library.InputStrDelegate
IL_01b3: stloc.s 8
// a = (Delegate.Combine(a, b) as InputStrDelegate);
IL_01b5: ldloc.s 8
IL_01b7: ldloc.s 7
IL_01b9: call class [System.Runtime]System.Delegate [System.Runtime]System
.Delegate::Combine(class [System.Runtime]System.Delegate, class [System.Runtime]System.Delegate)
IL_01be: isinst [Library]Library.InputStrDelegate
IL_01c3: stloc.s 8
// a = (Delegate.Remove(a, inputStrDelegate4) as InputStrDelegate);
IL_01c5: ldloc.s 8
IL_01c7: ldloc.s 6
IL_01c9: call class [System.Runtime]System.Delegate [System.Runtime]System
.Delegate::Remove(class [System.Runtime]System.Delegate, class [System.Runtime]System.Delegate)
IL_01ce: isinst [Library]Library.InputStrDelegate
可以看到使用+=/-=时其实就是调用了Delegate的Combine与Remove方法
加群入久伴博客官方微信群
加入久伴官方微信群有啥优势?
1.文章内部资源由于时间久远可能失效,加群,群内可以第一时间反馈
2.网站中教程不一定适用任何人,加群可以第一时间咨询并解决您得疑惑
3.你游荡于互联网,也需要群体,加群一起互动交流,畅所欲言
扫描下方二维码即可加入交流讨论
