委托知识整理笔记
一、委托基础
1.1 委托是什么
与类、枚举、结构、接口一样,委托也是一种类型。类是对对象的抽象,委托是对函数的抽象。一个委托代表了具有相同参数列表和返回值的(具有相同签名(signature))所有函数。
1.2 为什么要使用委托?能给我带来哪些好处?
有时在程序设计时,某個方法在执行时可能需要根据不同情形做不同处理并且这些情形根据时间可能还会有变动,所以这部分代码没法写死。所以C#就设计了委托来处理这些情形,将这些变化不定的细节交由委托来处理,委托来代理多个类似的处理方法。
这样使用委托方法作为方法参数的做法,具体执行细节由被代理方法完成的方式。可以避免在程序中大量使用If-Else(Switch)判断语句,同时使得程序具有更好的可扩展性。
1.3 如何使用?使用方法步骤有哪些呢?
e.g:
1 //第一步.声明一个委托 2 3 delegate int DelegateAdd(int a, int b); 4 5 public class Test 6 7 { 8 9 //第二步.提供要处理方法10 11 public static int Add(int a, int b)12 {13 14 int c = 0;15 16 c = a + b;17 18 return c;19 20 }21 22 }23 24 //入口类25 26 public class TestB27 28 {29 30 public static void Main()31 32 {33 34 //第三步.实例一个委托35 36 DelegateAdd da = new DelegateAdd (Test.Add);37 38 //4第四步调用委托39 40 int sum = da(2, 2);41 42 Console.WriteLine(sum.ToString());43 44 }45 46 }
上面写法是一种形式(静态方法或实例方法),下面两种方式也可实现:
匿名方法:
DelegateAdd da = delegate(int a, int b){
return a+b;
}
Lambda表达式方法:
DelegateAdd da = (a,b)=>a+b;
1.4 委托链
委托可以用 “+=” 和 “-=” 进行增加与删除委托实例;当增加多个委托实例时就形成了委托链表,即委托链。调用时,按照方法被添加的顺序依次执行。
1.5 泛型委托
泛型委托支持在委托返回值和参数上应用参数类型。
delegate void GenericsDelegate<T>(T value); public class GenericsDelegateClass { public static void F(int i) { Console.WriteLine("调用 F(int i) 方法 i="+i); } public static void G(string s) { Console.WriteLine("调用 G(string s) 方法 s="+s); } public static void Test() { GenericsDelegate<string>p2=G; p2("test"); GenericsDelegate<int>p1= new GenericsDelegate<int>(F); p1(222); } }
几个常用泛型委托
Action<T,…>是无返回值的泛型委托,这个委托类存在不同的变体,至少0个参数,至多16个参数;
Func<…T>是必须有返回值T的泛型委托,也存在不同的变体,至少0个参数,至多16个参数;
predicate <T>是返回值为bool的泛型委托,只有1个参数;表示定义一组条件并确定指定对象是否符合这些条件的方法。此委托由 Array 和 List 类的几种方法使用,用于在集合中搜索元素。
public class DelegateClass { public DelegateClass() { } public static void Print<T>(T v) { Console.WriteLine(v.ToString()); } public static int Add(int a, int b) { return a+b; } public static double AddTow(double a) { return a+ 2; } public static void Test() { Action<int>acInt= new Action<int>(Print<int>); acInt(3); //3 Action<string>acString=Print<string>; acString("hello string");//hello string Func<int, int, int>f1= new Func<int, int, int>(Add); Console.WriteLine(f1(2, 3));//5 Func<double, double>f2=AddTow; Console.WriteLine(f2(33.45));//35.45
List<int>list= new List<int>() { 1,2,3,4,5,6,7,8,9,10}; Predicate<int>p= new Predicate<int>((v) =>v< 5); var LessThanFive=list.Find(p); if (LessThanFive!= 0) { Console.WriteLine(LessThanFive);// 输出第一匹配1 } Predicate<int>p1= new Predicate<int>((v) =>v>= 11); int NotLessThanTen=list.Find(p1); if (NotLessThanTen!= 0) { Console.WriteLine(NotLessThanTen); } else { Console.WriteLine("未找到符合条件的元素");//未找到符合条件的元素 } } }
二、委托的同步调用、异步调用与异步回调
同步调用
以上介绍的委托调用方法都是同步调用。其实也可以Invoke方法用来进行同步调用。
如:f1.Invoke(1,2);同步调用也可以叫阻塞调用,它将阻塞当前线程,然后执行调用,调用完毕后再继续向下进行。如果是要调用一项耗时操作,可能会让程序停顿很长时间,造成糟糕的用户体验,这时候异步调用就很有必要了。
异步调用
异步调用不阻塞线程,而是把调用塞到线程池中,程序主线程或UI线程可以继续执行。委托的异步调用通过BeginInvoke和EndInvoke来实现。
public static void PrintList(List<int>l) { foreach (var one in l) { Console.WriteLine(one.ToString()); System.Threading.Thread.Sleep(300); } } public static void Test() { List<int>list= new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; Action<List<int>>acList=PrintList; acList.BeginInvoke(list,null,null); System.Threading.Thread.Sleep(600); Console.WriteLine("其他方法"); acList.EndInvoke(result); Console.WriteLine("其他操作");}
运行结果:
可以看到,主线程并没有等待,而是直接向下运行了。但是问题依然存在,当主线程运行到EndInvoke时,如果这时调用没有结束(这种情况很可能出现),这时为了等待调用结果,线程依旧会被阻塞。
异步回调
用回调函数,当调用结束时会自动调用回调函数,解决了为等待调用结果而让线程依旧被阻塞的局面。
public static void PrintList(List<int>l) { foreach (var one in l) { Console.WriteLine(one.ToString()); System.Threading.Thread.Sleep(300); } } static void MyCallBack(IAsyncResultresult) { Action<List<int>>handler= (Action<List<int>>)result.AsyncState; handler.EndInvoke(result); Console.WriteLine("异步执行完毕"); } public static void Test() { List<int>list= new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; Action<List<int>>acList=PrintList; IAsyncResultresult=acList.BeginInvoke(list, new AsyncCallback(MyCallBack), acList); System.Threading.Thread.Sleep(600); Console.WriteLine("其他方法");
//acList.EndInvoke(result); Console.WriteLine("其他操作");}
执行结果:
三、应用场景
委托可应用于Observer模式(观察者模式)、子窗口传值、异步更新主界面(如:进度条)等。
以winform进度条为例:
namespace DelegateTest{ public delegate void DelegateMethod(ProgressBar pb, int position); public partial class MainForm : Form { public MainForm() { InitializeComponent(); } public void progressBarDel(ProgressBar pb, int position) { if (pb.InvokeRequired)//等待异步 { DelegateMethod fc=new DelegateMethod(progressBarDel); this.Invoke(fc,pb, position);//通过代理调用刷新方法 } else { this.progressBar1.Value=position; } } private void MainForm_Load(object sender, EventArgs e) { this.progressBar1.Maximum= 100; this.progressBar1.Value= 0; Thread t=new Thread(progressThread); t.Start(); } void progressThread() { for (int i= 1; i< 100; i++) { progressBarDel(this.progressBar1, i); System.Threading.Thread.Sleep(1000); } } }}