c#知识总结4-进阶(委托)

目录

委托是什么

命名委托类型

匿名委托

Lambda表达式

泛型委托

委托的特性

委托与事件的关系

异步委托 (同步调用、异步调用、异步回调)

 1.同步调用

 2.异步有阻塞

​编辑3.异步有阻塞加超时

 4.异步无阻塞

 5.异步无阻塞+回调


委托是什么

委托是一种类型,他允许您将方法作为参数传递给其他方法,或者从方法中返回另一个方法。委托提供可一种灵活的方式来实现回调机制、事件处理和异步编程等功能。通俗来讲,就是委托包含方法的内存地址,方法匹配与委托相同的签名,因此通过使用正确的参数类型来调用方法。

委托的定义

//语法
delegate <return type> <delegate-name> <parameter list>
//实例
public delegate int MyDelegate (string s);

简单的实例用法

using System;

public delegate int AddDelegate(int a, int b);
namespace DelegateAppl
{
    public class Calculator
    {
        public int Add(int a, int b)
        {
            return a + b;
        }
    }

      static void Main(string[] args)
      {
        //普通调用
        Calculator calculator = new Calculator();
        // 调用加法方法
        int result = calculator.Add(3, 4);
        WriteLine("Result (Traditional): " + result);
         
        //委托调用
       // 创建委托实例并关联加法方法
        AddDelegate addDelegate = new AddDelegate(calculator.Add);
        // 通过委托调用加法方法
        int result = addDelegate(3, 4);
        Console.WriteLine("Result (Delegate): " + result);
         Console.ReadKey();
      }
  
}

这只是简单的用法介绍,可以看出 这和在主程序里直接调用方法没啥区别,并不是实际的用法。既然差不多,那这样做有什么意义?接下来我们了解下委托的一些特性。

命名委托类型
// 定义委托类型
public delegate void MyDelegate(string message);
​
// 使用委托类型
MyDelegate delegateInstance =new MyDelegate(SomeMethod);
//或者,简介写法
//MyDelegate delegateInstance = SomeMethod;
delegateInstance("Hello, Delegate!");
匿名委托

使用匿名方法来定义委托

//使用匿名委托
myDelegate delegateInstance=delegate(string message){Console.WriteLine(message); };
delegateInstance("Hello, Anonymous Delegate!");
Lambda表达式

使用Lambda表达式来定义委托,提供一种间接的语法。

// 使用 Lambda 表达式
MyDelegate delegateInstance = message => Console.WriteLine(message);
delegateInstance("Hello, Lambda Delegate!");
泛型委托

使用泛型委托Action或Func。避免为每个委托类型都定义新的委托。

// 使用 Action 委托
Action<string> actionDelegate = message => Console.WriteLine(message);
actionDelegate("Hello, Action Delegate!");
​
// 使用 Func 委托
Func<int, int, int> addDelegate = (a, b) => a + b;
int result = addDelegate(3, 4);
Console.WriteLine("Result: " + result);
委托的特性
  • 引用方法:委托允许存储对方法的引用,使得方法可以被动的调用。
  • 类型安全:委托是类型安全的。他在编译是会检查方法签名,确保委托实例只能引用与其声明的相同签名的方法。
  • 多播性:委托支持多播,即一个委托实例可以引用多个方法。通过+=或-=操作符,可以动态的添加或移除委托链中的方法。
using System;

public delegate int AddDelegate(int a, int b);
namespace DelegateAppl
{
    public class Calculator
    {
        public int Add(int a, int b)
        {
            return a + b;
        }
        public int Add1(int a, int b)
        {
            return a * b;
        }
    }
    internal class Program
    {
        static void Main(string[] args)
        {
            var calculator = new Calculator();
            AddDelegate ad = calculator.Add;
            AddDelegate ad1 = calculator.Add1;
            ad += ad1;
            var result = ad(3, 2);

            Console.WriteLine("Result (Delegate): " + result);
            //Result (Delegate):6
            Console.ReadKey();
        }
    }

}
  • 异步编程:委托在异步编程中扮演了重要的角色,尤其是在使用BeginInvoke和EndInvoke进行异步操作时。可参考下面章节,委托同步异步调用
  • 匿名方法和Lambda表达式:C#支持使用匿名方法和Lamdba表达式来创建简洁的委托实例,减少了样板代码的编写。
  • 委托泛型:c#提供了泛型委托Action和Func,可以动态的指定方法参数和返回类型,从而是的委托更加灵活和通用。

委托与事件的关系
  • 事件依赖于委托:事件本质上是一个特殊类型的委托,它用于封装事件的调用列表。在事件声明中,委托类型用于指定事件的签名。
  • 订阅和取消订阅:在事件中,其他对象可以通过订阅(添加委托方法)或取消订阅(移除委托方法)来注册或注销对事件的关注。
  • 封装性:事件提供了一种封装的方式来实现观察者模式。对象可以注册事件,而不需要知道具体有哪些对象在监听事件。
  • 安全性:通过事件,可以限制其他对象对委托调用列表来直接访问。从而提高代码的安全性和可靠性。
异步委托 (同步调用、异步调用、异步回调)
Invoke开始同步调用委托(同步调用Invoke,无论是否新开线程都会导致阻塞invoke所在的线程)
BeginInvoke开始异步调用委托
EndInvoke返回异步调用结果集(当使用BeginInvoke异步调用方法时,如果方法未执行完,EndInvoke方法就会一直阻塞,直到被调用的方法执行完毕)
AsyncCallback结束异步调用后执行的操作

注:

  • (control的Invoke和BeginInvoke)是在control线程上调用。所以如果在control线程上调用Invoke或BeginInvoke来进行调用某个委托方法来达到跨线程或异步是错误的。那Control的Invoke和BeginInvoke的用途是什么呢,它的用途是让其他线程的某个方法在control所在的线程上执行。如果在其他线程上调用control的Invoke,这时候其他线程会阻塞,直到control线程执行完其他线程才会继续执行。但control的BeginInvoke不会让其他线程阻塞。
  • (Delegate的Invoke和BeginInvoke)是在线程池中调用。所以一般如果牧歌方法执行时间特别长,都会用Delegate的BeginInvoke执行,这样control线程就不会阻塞。弹药注意,如果在control线程中调用Delagate的Invoke,虽然Delegate的Invoke是从线程池的线程同步调用,但它还是会阻塞control线程的,所以要用Delegate的BeginInvoke才能实现异步。还有一种情况就是线程池上用Delagate的Invoke会导致线程池上Delegate所在的线程被阻塞,直到control所在线程上执行完线程池上Delegate所在线程才能继续执行。
  • 使用BeginInvoke时特别注意的就是BeginInvoke里面所用到的变量必须全部使用参数形式进去,以防异步时被其他线程改变量值。
 1.同步调用

通过Invoke方法来进行同步调用。同步调用也可以叫阻塞调用,他讲阻塞当前线程,然后执行调用,调用完毕后再继续向下进行。

using System;
using System.Threading;

delegate TResult MyGenericDelegate<T, TResult>(T arg);
namespace DelegateAppl
{

    internal class Program
    {
        static void Main(string[] args)
        {


            Func<string, string> getInfo1 = (string txt) => { /*调用接口1*/ Thread.Sleep(10000); return "1 " + txt; };
            var result = getInfo1.Invoke("aa");
            Console.WriteLine("继续做别的事情。。。");

            Console.WriteLine(result);
            Console.ReadKey();
        }
    }

}

 2.异步有阻塞

例如有一个程序现在分别调用接口1和接口2,并要获得两个接口的结果集后才能执行后面的代码。接口1调用耗时10s,接口2调用耗时5秒,如果是同步调用两个接口并要处理这两个结果的返回值,一共需要15秒。但如果我们使用异步同时调用这两个接口,那我们一般最多只需要10s就可以返回两个接口的结果集。我们使用BeginIncoke/EndInvoke来实现,调用EndInvoke时,调用线程会处于阻塞状态。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[] args)
        {
            //定义调用接口1委托
            Func<string, string> getInfo1 = (string txt) => { /*调用接口1*/ Thread.Sleep(10000); return "1"; };
            //定义调用接口2委托
            Func<string, string> getInfo2 = (string txt) => { /*调用接口2*/ Thread.Sleep(5000); return "2"; };

            //开始调用接口1
            IAsyncResult ar1 = getInfo1.BeginInvoke("调用接口1", null, null);
            //开始调用接口2
            IAsyncResult ar2 = getInfo2.BeginInvoke("调用接口2", null, null);

            Console.WriteLine("主线程执行1");//BeginInvoke异步,不会阻塞,输出“主线程执行2”
            string result1 = getInfo1.EndInvoke(ar1);//处理接口1返回结果(EndInvoke 阻塞异步线程,需等待线程完成)
            string result2 = getInfo2.EndInvoke(ar2);//处理接口2返回结果
            Console.WriteLine($"result1: {result1} result2: {result2}");
            Console.WriteLine("主线程执行2");
            Console.ReadKey();
        }
    }
}
3.异步有阻塞加超时

利用waitOne方法,这个方法的超时设置指代码运行但这局句代码时开始计算时间。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[] args)
        {
            //定义调用接口1委托
            Func<string, string> getInfo1 = (string txt) => { /*调用接口1*/ Thread.Sleep(1000); return "1"; };
            //定义调用接口2委托
            Func<string, string> getInfo2 = (string txt) => { /*调用接口2*/ Thread.Sleep(7000); return "2"; };

            //开始调用接口1
            IAsyncResult ar1 = getInfo1.BeginInvoke("调用接口1", null, null);
            //开始调用接口2
            IAsyncResult ar2 = getInfo2.BeginInvoke("调用接口2", null, null);

            //string result1 = getInfo1.EndInvoke(ar1);//处理接口1返回结果
            //string result2 = getInfo2.EndInvoke(ar2);//处理接口2返回结果

            int TimeOut = 5000;//超时时间为5秒
            var sw = new Stopwatch();
            sw.Start();

            if (ar1.AsyncWaitHandle.WaitOne(TimeOut))
            {
               var result11= getInfo1.EndInvoke(ar1); //方法成功执行返回值
                Console.WriteLine($"result1: {result11}");
                sw.Stop();
                TimeOut -= (int)sw.ElapsedMilliseconds;//减掉第一个异步的耗时
            }
            else
            {
                Console.WriteLine($"ar1 超时");
                sw.Stop();
                TimeOut = 0;
                //超时
            }
            
            Console.WriteLine($"TimeOut: {TimeOut}");
            //Thread.Sleep(6000); //加上此句,则ar2不会超时,因为ar1+sleep(6000)大于ar2耗时时间,ar2执行waitOne时,ar2已经执行完成,所以不会超时
            if (ar2.AsyncWaitHandle.WaitOne(TimeOut))
            {
               var result12= getInfo2.EndInvoke(ar2); //方法成功执行返回值
                Console.WriteLine($"result2: {result12}");
            }
            else
            {
                Console.WriteLine($"ar2 超时");
                //超时
            }

            
            Console.ReadKey();
        }
    }
}

 4.异步无阻塞

例如异步写日志,control线程吧要写入的日志信息传递给另外一个线程,不管另外一个线程的执行精度,control线程继续执行其他工作。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[] args)
        {
            //定义写日志委托
            Action<string> writeLog = (string log) => { /*写日志内容*/
                Thread.Sleep(2000);
                Console.WriteLine($"log: {log}"); };
            //在写日志委托回调函数李自动调用EndInvoke
            AsyncCallback callFun = (result) => { ((Action<string>)result.AsyncState).EndInvoke(result); };
            //开始异步写日志
            writeLog.BeginInvoke("日志内容", callFun, writeLog);
            Console.WriteLine("主进程 继续做别的事情");
            Console.ReadKey();
        }
    }
}

 5.异步无阻塞+回调

例如一个UI程序按钮需要调用接口1和接口2,并获取两个接口的结果后更新按钮上的文字。接口1调用耗时10s,接口2调用耗时5s,同步调用需要耗时15s,UI画面会卡死15秒,但我们使用上面2(异步有阻塞),UI至少卡死10s。此次我们可以使用AsyncCallback处理,将EndInvoke放在AsyncCallback中处理,当我们按下按钮是,会异步调用这两个接口,有雨UI线程没有被阻塞,所以界面不会卡死,当接口调用完成后将自动调用AsyncCallback。

private void asynbtn_Click(object sender, EventArgs e)
{
//定义调用接口1委托
Func<string, string> getInterface1 = (string txt) => { Thread.Sleep(5000); return "1"; };
//定义调用接口2委托
Func<string, string> getInterface2 = (string txt) => { Thread.Sleep(7000); return "2"; };

//异步全局信息
AsyncGlobalInfo agi = new AsyncGlobalInfo();
agi.gi1 = getInterface1;
agi.gi2 = getInterface2;

//开始调用接口1
IAsyncResult ar1 = getInterface1.BeginInvoke("调用接口1参数", new AsyncCallback((result) => { AsyncGlobalInfo ic = (AsyncGlobalInfo)result.AsyncState; ic.result1 = ic.gi1.EndInvoke(result); ic.isCompleted1 = true; setText(ic); }), agi);
//开始调用接口2
IAsyncResult ar2 = getInterface2.BeginInvoke("调用接口2参数", new AsyncCallback((result) => { AsyncGlobalInfo ic = (AsyncGlobalInfo)result.AsyncState; ic.result2 = ic.gi2.EndInvoke(result); ic.isCompleted2 = true; setText(ic); }), agi);
}
//定义处理按钮委托
public void setText(AsyncGlobalInfo infoall)
{
//判断所有的异步操作都完成
if (infoall.isCompleted1 && infoall.isCompleted2)
{
this.Invoke(new Action(() => { this.button1.Text = infoall.result1 + " " + infoall.result2; }));
}
}

/// <summary>
/// 异步全局信息
/// </summary>
public class AsyncGlobalInfo
{
public Func<string, string> gi1;
public bool isCompleted1 = false;
public string result1 = null;
public Func<string, string> gi2;
public bool isCompleted2 = false;
public string result2 = null;
}

异步无阻赛+回调2

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.Remoting.Messaging;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[] args)
        {
            定义写日志委托
            //Action<string> writeLog = (string log) => { /*写日志内容*/
            //    Thread.Sleep(2000);
            //    Console.WriteLine($"log: {log}"); };
            在写日志委托回调函数李自动调用EndInvoke
            //AsyncCallback callFun = (result) => { ((Action<string>)result.AsyncState).EndInvoke(result); };
            开始异步写日志
            //writeLog.BeginInvoke("日志内容", callFun, writeLog);


            Console.WriteLine("===== 异步回调 AsyncInvokeTest =====");
            Func<int, int, int> handler = (int a, int b) => {
                Thread.Sleep(2000);
                return a + b; 
            };

            //异步操作接口(注意BeginInvoke方法的不同!)
            IAsyncResult result = handler.BeginInvoke(1, 2, new AsyncCallback(callBack), "AsycState:OK");

            Console.WriteLine("继续做别的事情。。。");
            Console.ReadKey();




        }

        static void callBack(IAsyncResult result)
        {      //result 是“加法类.Add()方法”的返回值

            //AsyncResult 是IAsyncResult接口的一个实现类,空间:System.Runtime.Remoting.Messaging
            //AsyncDelegate 属性可以强制转换为用户定义的委托的实际类。
            Func<int,int,int> handler = (Func<int,int,int>)((AsyncResult)result).AsyncDelegate;
            Console.WriteLine(handler.EndInvoke(result));
            Console.WriteLine(result.AsyncState);
        }
    }
}

注:在.net core中,已放弃BeginInvoke的使用,可能会报错,推荐Task。(可参考后续的文章)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值