委托(delegate)
委托概述
将方法调用者和目标方法动态关联起来,委托是一个类,所以它和类是同级的,可以通过委托来掉用方法,不要误以为委托和方法同级的,方法只是类的成员。委托定义了方法的类型(定义委托和与之对应的方法必须具有相同的参数个数,并且类型相同,返回值类型相同),使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If-Else(Switch)语句,同时使得程序具有更好的可扩展性。
基础委托(Delegate)
在.Net中声明委托使用关键词delegate,委托具有多种使用方式(以下均为同步委托调用):
在这里插入代码片
同步委托&异步委托
同步委托
委托的Invoke方法用来进行同步调用。同步调用也可以叫阻塞调用,它将阻塞当前线程,然后执行调用,调用完毕后再继续向下进行。
异步委托
异步调用不阻塞线程,而是把调用塞到线程池中,程序主线程或UI线程可以继续执行。委托的异步调用通过BeginInvoke和EndInvoke来实现。
以下为异步委托调用方式:
在这里插入代码片
异步回调(Callback)
异步回调通过设置回调函数,当调用结束时会自动调用回调函数,可以在回调函数里触发EndInvoke,这样就释放掉了线程,可以避免程序一直占用一个线程。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace 委托概述
{
class Program
{
public delegate void DelegateNoReturnWithParameters(string o);
public delegate void DelegateReturnWithParameters(string o);
static void Main(string[] args)
{
Console.WriteLine($"主线程执行{Thread.CurrentThread.ManagedThreadId}");
/*
BeginInvoke方法参数个数不确定, 最后两个参数含义固定,如果不使用的话,需要赋值null
委托的方法无参数,这种情况下BeginInvoke中只有两个参数。
此外,委托的方法有几个参数,BeginInvoke中从左开始,对应响应的参数。
1.倒数第二个参数:是有一个参数值无返回值的委托,它代表的含义为,该线程执行完毕后的回调。
2.倒数第一个参数:向即回调中传值,用AsyncState来接受。
3.其它参数:对应委托方法的参数。
*/
DelegateReturnWithParameters methord = new DelegateReturnWithParameters(Test);
IAsyncResult asyncResult = methord.BeginInvoke("demo-ok",
new AsyncCallback(Callback),
"AsycState:给回调函数的参数传递在此处出传值");
Console.WriteLine($"主线程执行{Thread.CurrentThread.ManagedThreadId}-AsyncState--{asyncResult.AsyncState}");
Console.WriteLine($"主线程执行{Thread.CurrentThread.ManagedThreadId}-IsCompleted:--{asyncResult.IsCompleted}");
Console.ReadKey();
}
private static void Callback(IAsyncResult asyncResult)
{
/*
*asyncResult为回调前异步调用方法返回值
*AsyncResult 是IAsyncResult接口的一个实现类,引用空间:System.Runtime.Remoting.Messaging
*AsyncDelegate 属性可以强制转换为定义的委托类型
*/
DelegateReturnWithParameters methord = (DelegateReturnWithParameters)((System.Runtime.Remoting.Messaging.AsyncResult)asyncResult).AsyncDelegate;
methord.EndInvoke(asyncResult);
Console.WriteLine($"主线程执行{Thread.CurrentThread.ManagedThreadId}-AsyncState--{asyncResult.AsyncState}");
Console.WriteLine($"主线程执行{Thread.CurrentThread.ManagedThreadId}-IsCompleted:--{asyncResult.IsCompleted}");
}
/// <summary>
/// 定义有参无返回值测试方法
/// </summary>
/// <param name="o"></param>
private static void Test(string o)
{
Console.WriteLine("有参无返回值:{0}", o);
}
}
}
注意:
异步委托线程等待
1.【Delegate】.EndInvoke(推荐)
1 public delegate void NoReturnWithParameters(string o);
2 NoReturnWithParameters noReturnWithParameters = new NoReturnWithParameters(...);
3 ......
4 noReturnWithParameters.EndInvoke(asyncResult);
2.【IAsyncResult】.AsyncWaitHandle.WaitOne(可以定义等待时间,超过等待时间不继续等待向下执行)
1 IAsyncResult asyncResult = null;
2 asyncResult.AsyncWaitHandle.WaitOne(2000);//等待2000毫秒,超时不等待
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace 委托概述
{
class Program
{
public delegate void DelegateNoReturnWithParameters(string o);
public delegate void DelegateReturnWithParameters(string o);
static void Main(string[] args)
{
Console.WriteLine($"主线程执行{Thread.CurrentThread.ManagedThreadId}");
/*
BeginInvoke方法参数个数不确定, 最后两个参数含义固定,如果不使用的话,需要赋值null
委托的方法无参数,这种情况下BeginInvoke中只有两个参数。
此外,委托的方法有几个参数,BeginInvoke中从左开始,对应响应的参数。
1.倒数第二个参数:是有一个参数值无返回值的委托,它代表的含义为,该线程执行完毕后的回调。
2.倒数第一个参数:向即回调中传值,用AsyncState来接受。
3.其它参数:对应委托方法的参数。
*/
DelegateReturnWithParameters methord = new DelegateReturnWithParameters(Test);
IAsyncResult asyncResult = methord.BeginInvoke("demo-ok",
new AsyncCallback(Callback),
"AsycState:给回调函数的参数传递在此处出传值");
;
while (!asyncResult.IsCompleted&& asyncResult.AsyncWaitHandle.WaitOne(2000))
{
Console.WriteLine($"主线程执行等待中。。。{Thread.CurrentThread.ManagedThreadId}-AsyncState--{asyncResult.IsCompleted}");
}
Console.WriteLine($"主线程执行{Thread.CurrentThread.ManagedThreadId}-IsCompleted:--{asyncResult.IsCompleted}");
Console.WriteLine($"主线程执行{Thread.CurrentThread.ManagedThreadId}-AsyncState--{asyncResult.AsyncState}");
Console.ReadKey();
}
private static void Callback(IAsyncResult asyncResult)
{
/*
*asyncResult为回调前异步调用方法返回值
*AsyncResult 是IAsyncResult接口的一个实现类,引用空间:System.Runtime.Remoting.Messaging
*AsyncDelegate 属性可以强制转换为定义的委托类型
*/
DelegateReturnWithParameters methord = (DelegateReturnWithParameters)((System.Runtime.Remoting.Messaging.AsyncResult)asyncResult).AsyncDelegate;
methord.EndInvoke(asyncResult);
Console.WriteLine($"执行委托的方法的线程状态为:{Thread.CurrentThread.ManagedThreadId}-AsyncState--{asyncResult.AsyncState}");
Console.WriteLine($"执行委托的方法的线程完成状态为:{Thread.CurrentThread.ManagedThreadId}-IsCompleted:--{asyncResult.IsCompleted}");
}
/// <summary>
/// 定义有参无返回值测试方法
/// </summary>
/// <param name="o"></param>
private static void Test(string o)
{
Thread.Sleep(2000);
Console.WriteLine($"执行委托的方法的线程为:{Thread.CurrentThread.ManagedThreadId}");
}
}
}
3.【IAsyncResult】.IsCompleted(是IAsyncResult对象的一个属性,该值指示异步操作是否已完成。不推荐)
1 IAsyncResult asyncResult = xxx.BeginInvoke(...);
2 while (!asyncResult.IsCompleted)
3 {
4 //正在等待中
5 }
内置委托(泛化委托)
.Net Framework 提供两个支持泛型的内置委托,分别是Action<>和Func<>,在System命名空间中定义,结合lambda表达式,可以提高开发效率。
Net Framework 提供两个支持泛型的内置委托,分别是Action<>和Func<>,在System命名空间中定义,结合lambda表达式,可以提高开发效率。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace 委托概述
{
class 内置委托泛化委托
{
static void Main(string[] args)
{
//使用Action声明委托
Action<string> action = TestAction;
action.Invoke("action-demo-ok");
//使用Func声明委托
Func<string, string> func = TestFunc;
string result = func.Invoke("func-demo-ok");
Console.WriteLine(result);
Console.ReadKey();
}
private static void TestAction(string o)
{
Console.WriteLine("TestAction方法执行成功:{0}", o);
}
private static string TestFunc(string o)
{
return "TestFunc方法执行成功:" + o;
}
}
}
Action:无返回值的泛型委托,目前.NET Framework提供了17个Action委托,它们从无参数到最多16个参数。public delegate void Action
Func:有返回值的泛型委托,.NET Framework提供了17个Func函数,允许回调方法返回值。
除此之外还有Predicate,它是固定返回值为bool类型的泛型委托。Action和Func足够使用这里不做介绍。
注意:
1.委托定义不要太多,微软仅在MSCorLib.dll中就有近50个委托类型,而且.NET Framework现在支持泛型,所以我们只需几个泛型委托(在System命名空间中定义)就能表示需要获取多达16个参数的方法。
2.如需获取16个以上参数,就必须定义自己的委托类型。所以建议尽量使用内置委托,而不是在代码中定义更多的委托类型,这样可以减少代码中的类型数量,同时简化编码。
3.如需使用ref或out关键字以传引用的方式传递参数,就需要定义自己的委托。
内置委托(泛化委托)参数协变&逆变
协变(out):假定S是B的子类,如果X(S)允许引用转换成X(B),那么称X为协变类。(支持“子类”向“父类”转换)
逆变(in):假定S是B的子类,如果X(B)允许引用转换成X(X),那么称X为协变类。(支持“父类”向“子类”转换)
正如泛化接口,泛型委托同样支持协变与逆变
委托揭秘
委托看似很容易使用,通过delegate关键词定义,用熟悉的new构造委托实例,熟悉的方式调用回调函数,但实际上编译器和CLR在幕后做了大量工作来隐藏其复杂性。
重新审视上面计算器的一段代码:
1 public delegate int MulticastInstance(int inputA, int inputB);
事实上通过反编译可看到:
编译器相当于定义了一个完整的类(继承自System.MulticastDelegate,定义四个方法:构造函数、Invoke、BeginInvoke和EndInvoke):
所有委托类型都派生自System.MulticastDelegate类,System.MulticastDelegate派生自System.Delegate,后者又派生自System.Object。历史原因造成有两个委托类。
创建的所有委托类型都将MulticastDelegate作为基类,个别情况下仍会用到Delegate。Delegate类的两个静态方法Combine和Remove的签名都指出要获取Delegate参数。由于创建的委托类型派生自MulticastDelegate,后者又派生自Delegate,所以委托类型的实例是可以传递给这两个方法的。
MulticastDelegate的三个重要非公共字段
结语
- 同步委托将阻塞当前线程,等待方法执行完毕继续执行程序,相当于直接调用方法。
- 异步委托是将方法放入线程池中执行并不阻塞主线程。
异步委托从根本上说并不是多线程技术(任务Task也一样),就算异步委托内部将方法塞给线程池去执行也并不能说是开辟新线程执行方法,(线程池一定开辟新线程)这种说法并不严谨。
- 委托本质是将调用者和目标方法动态关联起来,这是或许是我所理解的委托存在的最根本目的吧。