C#

C#回调

回调比较

Posted by Bob on January 5, 2019

早先在CSDN发表的《delegate,event, lambda,Func,Action》文章重新整理一下

delegate

delegate标示了函数回调的规范,能够拥有一个签名(signature),并且它只能持有与它的签名相匹配的方法的引用。它所实现的功能与C/C++中的函数指针十分相似。


using System;
 
namespace TestCSharp
{
    class Animation
    {
        //无返回无参数的delegate(委托、代理)
        public  delegate void PlayEndHandler();
        //定义event(事件),类型为上面定义的PlayEndHandler委托
        public event PlayEndHandler PlayEndCallBack;
 
        //有返回有参数
        public delegate string ActionHandler(int type,string message);
        public event ActionHandler ActionCallBack;
 
        public Animation()
        {
            //可以是成员方法
            PlayEndHandler handler = Move;
            handler();
 
            this.PlayEndCallBack = Move;
            this.PlayEndCallBack();
 
            //也可是静态方法
            PlayEndHandler runHandler = Run;
            runHandler();
 
            ActionHandler aHandler = Jump;
            aHandler(190,"You Jump!");
 
            //设置的方法必须和定义一致
            this.ActionCallBack = Jump;
            Console.WriteLine(this.ActionCallBack(10001,"Jumping"));
        }
 
        private void Move()
        {
            Console.WriteLine("Move");
        }
 
        private static void Run()
        {
            Console.WriteLine("Run");
        }
 
        private string Jump(int type,string message)
        {
            string content = String.Format("type:{0},message:{1}",type,message);
            Console.WriteLine(content);
            return content;
        }
 
 
    }
}

event

加入event关键字,编译器会自动针对事件生成一个私有的字段(与此事件相关的委托),以及两个访问器方法,即add访问器方法以及remove访问器方法,用于对事件的Subscribe注册及Unsubscribe注销(对事件使用+=和使用-=操作时就是调用的这两个方法)。实际上声明一个委托类型的字段也可以实现这些功能。 之所以采用event而不直接采用委托,还是为了封装。可以设想一下,如果直接采用公共的委托字段,类型外部就可以对此字段进行直接的操作了,比如将其直接赋值为null。 而使用event关键字就可以保证对事件的操作仅限于add访问器方法以及remove访问器方法(即只能使用+=及-=)


using System;
 
namespace TestCSharp
{
    class Animation
    {
        //无返回无参数的delegate(委托)
        public delegate void PlayEndHandler();
        //定义event(事件),类型为上面定义的PlayEndHandler委托
        public event PlayEndHandler PlayEndCallBack;
 
 
 
        public Animation()
        {
 
            this.PlayEndCallBack += Move;
            this.PlayEndCallBack += Run;
 
            this.PlayEndCallBack();
 
            this.PlayEndCallBack -= Run;
            this.PlayEndCallBack -= Move;
            if (this.PlayEndCallBack != null)
            {
                this.PlayEndCallBack();
            }
 
            PlayEndHandler d1 = Move;
            PlayEndHandler d2 = Run;
            this.PlayEndCallBack = d1 + d2;
            this.PlayEndCallBack -= Run;
            this.PlayEndCallBack();
            //Move,Run,Move
        }
 
        private void Move()
        {
            Console.WriteLine("Move");
        }
 
        private static void Run()
        {
            Console.WriteLine("Run");
        }
 
    }
}

如果多次注册同一个事件处理函数时,触发时处理函数是否也会多次触发?


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace TestCSharp
{
    class Animation
    {
        public delegate void PlayHandler();
        public event PlayHandler Callback;
 
        public Animation()
        {
            this.Callback += Move;
            this.Callback += Move;
            this.Callback += Move;
            this.Callback();
        }
 
        public void Move()
        {
            Console.WriteLine("Move");
        }
 
        public void Run()
        {
            Console.WriteLine("Run");
        }
 
        public void Jump()
        {
            Console.WriteLine("Jump");
        }
 
 
    }
}

//输出:Move Move Move

可以注册多次同一个函数,触发时会如上所示执行多次.

如果注册了一个事件处理函数,却执行了两次或多次”注销事件“,是否会报错?


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace TestCSharp
{
    class Animation
    {
        public delegate void PlayHandler();
        public event PlayHandler Callback;
 
        public Animation()
        {
            this.Callback += Move;
            this.Callback -= Move;
            this.Callback -= Move;
            this.Callback();
        }
 
        public void Move()
        {
            Console.WriteLine("Move");
        }
 
        public void Run()
        {
            Console.WriteLine("Run");
        }
 
        public void Jump()
        {
            Console.WriteLine("Jump");
        }
 
 
    }
}

image

注册一次,可以注销多次,但是要注意触发时要检查是否为空,做如下修改后避免如上图的空指针异常


            if (this.Callback != null)
            {
                this.Callback();
            }

如果不手动注销事件函数,系统会帮我们回收吗?


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace TestCSharp
{
    class EventSource
    {
        public delegate void PlayHandler();
        public event PlayHandler Callback;
 
        public void Excute()
        {
            if (this.Callback != null)
            {
                this.Callback();
            }
        }
 
    }
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace TestCSharp
{
    class Listener
    {
        public Listener(EventSource source)
        {
            source.Callback += Run;
        }
 
        private void Run()
        {
            Console.WriteLine("Run");
        }
 
        ~Listener()
        {
            Console.WriteLine("Listener 销毁");
        }
    }
}

using System;
using System.Collections;
using System.Collections.Generic;
 
namespace TestCSharp
{
 
    class MainClass
    {
        public static void Main(string[] args)
        {
            EventSource s = new EventSource();
            Listener l = new Listener(s);
            s.Excute();
            l = null;
            Gc();
            s.Excute();
            s = null;
            Gc();
        }
 
        public static void Gc()
        {
            Console.WriteLine("GC 开始");
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            Console.WriteLine("GC 结束");
        }
    }
   
}

image

注意第二次调用s.Excute()前,已将listener赋为null,并执行Gc,但是仍然输出Run。直到将source赋为null时,listener才被回收掉。 下面我们手工注销事件,看看系统能不能自动回收?


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace TestCSharp
{
    class Listener
    {
        public Listener(EventSource source)
        {
            source.Callback += Run;
        }
 
        //添加一个手工注销方法
        public void DeregisterEvent(EventSource source)
        {
            source.Callback -= Run;
        } 
 
        private void Run()
        {
            Console.WriteLine("Run");
        }
 
        ~Listener()
        {
            Console.WriteLine("Listener 销毁");
        }
    }
}

using System;
using System.Collections;
using System.Collections.Generic;
 
namespace TestCSharp
{
 
    class MainClass
    {
        public static void Main(string[] args)
        {
            EventSource s = new EventSource();
            Listener l = new Listener(s);
            s.Excute();
            //手工调用注销
            l.DeregisterEvent(s);
            l = null;
            Gc();
            s.Excute();
            s = null;
            Gc();
        }
 
        public static void Gc()
        {
            Console.WriteLine("GC 开始");
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            Console.WriteLine("GC 结束");
        }
    }
   
}

这次我们可以看到run执行一次,listener也早早的被回收了。

更多系统回收资料参见Weak Events in C#

lambda


using System;
 
namespace TestCSharp
{
    class Animation
    {
        //无返回无参数的delegate(委托)
        public delegate void PlayEndHandler();
        //定义event(事件),类型为上面定义的PlayEndHandler委托
        public event PlayEndHandler PlayEndCallBack;
 
        public delegate void RunEndHandler(string message);
        public event RunEndHandler RunCallBack;
 
        public Animation()
        {
            //委托回调
            this.PlayEndCallBack += Move;
            //匿名委托
            this.PlayEndCallBack += delegate { Console.WriteLine("Jump"); };
            //匿名lambda
            this.PlayEndCallBack += () => Console.WriteLine("Fly");
 
            this.PlayEndCallBack();
 
            this.RunCallBack += delegate(string name) { Console.WriteLine("Jump"); };
            this.RunCallBack += (string name) => Console.WriteLine("Fly");
            this.RunCallBack("cab");
        }
 
        private void Move()
        {
            Console.WriteLine("Move");
        }
 
 
        private void Run(string message)
        {
            Console.WriteLine("Run");
        }
    }
}

如何认定两个事件处理函数是一样的? 如果是匿名函数呢?


using System.Text;
 
namespace TestCSharp
{
    class Animation
    {
        public delegate void PlayHandler();
        public event PlayHandler Callback;
 
        public Animation()
        {
            this.Callback += ()=> Console.WriteLine("Jump");
            //Event Unsubscription Via Anonymous Delegate
            this.Callback -= () => Console.WriteLine("Jump");
        }
 
    }
}

Resharper给出提示Event Unsubscription Via Anonymous Delegate,看起来一致的匿名函数,实际上方法签名是不一样,所以不能如上代码所示取消订阅一个匿名方法。如下代码给出如何取消订阅匿名方法的解决方案。


using System.Text;
 
namespace TestCSharp
{
    class Animation
    {
        public delegate void PlayHandler();
        public event PlayHandler Callback;
 
        public Animation()
        {
            PlayHandler func = () => Console.WriteLine("Jump");
            this.Callback += func;
            this.Callback -= func;
            this.Callback += func;
            this.Callback();
        }
 
    }
}

更多取消匿名事件的资料参见官方文档

Func

Func:有输入参数,还有返回值的泛型委托


using System;
 
namespace TestCSharp
{
    class Animation
    {
        //无参数,返回值为string
        public Func<string> callback;
        //参数为bool,int,返回值为string
        public Func<bool,int,string> playCallback1;
        //有多种方法重载
        public Func<int,string> playCallback2;
 
        public Animation()
        {
            this.callback = Move;
            Console.WriteLine(this.callback());
 
            this.playCallback1 = Run;
            Console.WriteLine(this.playCallback1(false,10001));
 
            this.playCallback2 = Run;
            Console.WriteLine(this.playCallback2(10002));
        }
 
        private string Move()
        {
            return "Move";
        }
 
        private string Run(bool isRun,int type)
        {
            return "Run1";
        }
 
        private string Run( int type)
        {
            return "Run2";
        }
    }
}

Action

Action:只有输入参数,无返回值的泛型委托


using System;
 
namespace TestCSharp
{
    class Animation
    {
        //无参数,返回值为string
        public Action<string> callback;
        //参数为bool,int,返回值为string
        public Action<bool, int> playCallback1;
        //有多种方法重载
        public Action playCallback2;
 
        public Animation()
        {
            this.callback = Move;
            this.callback("Hi");
 
            this.playCallback1 = Run;
            this.playCallback1(false, 10001);
 
            this.playCallback2 = Jump;
            this.playCallback2();
        }
 
        private void Move(string message)
        {
            Console.WriteLine("Move");
        }
 
        private void Run(bool isRun,int type)
        {
            Console.WriteLine("Run");
        }
 
        private void Jump()
        {
            Console.WriteLine("Jump");
        }
    }
}

predicate

predicate:返回bool型的泛型委托, predicate 表示传入参数为int ,返回bool的委托, Predicate有且只有一个参数,返回值固定为bool


using System;
 
namespace TestCSharp
{
    class Animation
    {
 
        public Predicate<int> Callback;
 
        public Animation()
        {
            this.Callback = Move;
            Console.WriteLine(this.Callback(1));
        }
 
        private bool Move(int type)
        {
            Console.WriteLine("Move");
            return true;
        }
 
    }
}