zoukankan      html  css  js  c++  java
  • [C#] Delegate, Multicase delegate, Event

    声明:这篇博客翻译自:https://www.codeproject.com/Articles/1061085/Delegates-Multicast-delegates-and-Events-in-Csharp

    第一次翻译英文博客,由于水平(技术水平+英语理解能力)有限/不足,肯定会有所疏漏/错误,请及时指正。

    介绍:

    在网络上搜索一下关于C#代理,事件,多播代理的文章,很多很多。不过还是有些地方讲的不明白/透彻。这篇博客将以简单易懂的方式来讲解这3个概念。下面先回答一下:

    • 什么是delegate,在哪里使用delegate?
    • 什么是multicast delegate?
    • 何时使用delegate,Event

    简要的答案:

    下面的图解解释了delegate,multicase delegate, event之间的联系。

    • Delegate是方法的指针,可以用作回调函数;
    • Multicast delegate用作调用多个回调函数;
    • Event封装了delegate,并且实现了订阅-发布模型;
    • Event和multicase delegate都是delegate,因此delegate是event和multicase delegate的基础;

    在本篇文章的剩余部分,我们将来更详细的理解上面这些话。

    Delegate是方法指针

    “Delegate是方法的指针”,你可以通过delegate来调用指向的方法。

    通过下面3步创建delegate:

    声明delegate,PS: 在这一步你将使用delegate关键字来声明委托,注意必须保证delegate的签名和需要指向的函数/方法签名一样/一致。例如下面代码中“SomeMethod()"无返回值且无输入参数。因此示例代码中的"SomeMethodPtr()"delegate定义是合适的;

    创建一个delegate对象,PS: 当你创建了一个delegate之后,需要创建一个delegate的对象才能使用它,参考代码中注释Step2;

    调用delegate,PS: 通过调用delegate的Invoke方法调用"SomeMethod".

        delegate void SomeMethodPtr();  // 1. Declare delegate
    
        static void Main(string[] args)
        {
            SomeMethodPtr ptrObject = SomeMethod;   // 2. Create object of delegate
            ptrObject.Invoke(); // 3. Invoke the delegate
        }
    
        static void SomeMethod()
        {
            // some code
        }

    为什么要使用这种见解调用呢?

    目前我们理解了delegate是方法/函数的指针。通过delegate间接调用一个方法有什么好处呢?

    当我们需要调用的code在另外一个程序集中,例如下面的文件搜索类在一个其他assebmly中。PS:这个文件搜索代码只是一个模拟代码。

    public class SearchFile
    {
        public void Search()
        {
            // File search is happening
            for(int i=0;i<100;i++)
            {
                string str = "File " + i;
            }
        }
    }

    现在我们在另外一个单独的Console应用中调用上面的代码,当搜索完毕后,立刻通知UI并显示搜索的文件名。

    static void Main(string[] args)
    {
        SearchFile fl = new SearchFile();
        fl.Search();
    }

    换句话所我们需要一个CALLBACK(回调函数)当File Search结束后通知Console程序。这就是为什么说delegate是用来做回调函数的。

    很多开发者想为什么不在Searh()方法中直接调用“Console.Write”进行输出呢。这样做会将UI技术和核心代码有耦合。如果我们把这段代码给WPF/WinForms程序调用,Console.Write做UI输出不适用.

        public class SearchFile
        {
            public void Search()
            {
                // File search is happening
                for (int i = 0; i < 100; i++)
                {
                    string str = "File" + i;
                    Console.Write(str);
                }
            }
        }

    使用delegate实现一个回调函数
    在SearchFile类中实现delegate,第一件事是暴露一个delegate的调用。在下面的SearchFile类中,定义一个WheretoCall的delegate,仔细观察Step1,2中的代码,此时的WheretoCall是空的,任何一个客户端程序想要有一个回调事件,传递一个签名一致的方法即可。

        public class SearchFile
        {
            public delegate void WheretoCall(string status);    // Step1
    
            public WheretoCall wheretoCall = null;      // Step 2
    
            public void Search()
            {
                // File search is happening
                for (int i = 0; i < 100; i++)
                {
                    string str = "File" + i;
                    wheretoCall(str);       // Step 3
                }
            }
        }

    现在调用上述代码时只需要传递一个方法的引用到delegate指针即可。请看下面代码中Step 1

        static void Main(string[] args)
        {
            SearchFile fl = new SearchFile();
            fl.wheretoCall = CallHere;  // Step 1
            fl.Search();
        }
    
        static void CallHere(string message)
        {
            Console.Write(message);
        }

    Multicast delegate(多播委托)

    上面图示中SearchFile"类发送通知(广播)到3个订阅方法。我们可以把这种模式称为订阅发布架构。SearchFile类是发布者。

        static void CallHereToWriteToFile(string message)
        {
            System.IO.File.WriteAllText(@"c:somtext.txt", message);
        }
        static void CallHereForConsole(string message)
        {
            Console.WriteLine(message);
        }
        static void CallHereToWriteInternally(string message)
        {
            messages.Add(message);
        }

    为了实现上述目标,我们不需要修改SearchFile类,在客户端调用时使用“+=”将方法分配给“wheretoCall”代理。如果你不想订阅,使用"-="取消订阅即可。

        static void Main(string[] args)
        {
            SearchFile fl = new SearchFile();
            fl.wheretoCall += CallHereForConsole;
            fl.wheretoCall += CallHereToWriteToFile;
            fl.wheretoCall += CallHereToWriteInternally;
            fl.Search();
        }

    Event -- 封装后的delegate

    通过multicast delegate实现的订阅-发布者模式有一个严重的问题,订阅者可以修改这个delegate。在一个真实的订阅-发布者模式/广播模式中,订阅者只能订阅/取消订阅。

    下面的代码中,订阅者可以将发布者delegete设置为NULL,并且可以自由调用delegate。

        static void Main(string[] args)
        {
            SearchFile f1 = new SearchFile();
            f1.wheretoCall += CallHereForConsole;
            f1.wheretoCall.Invoke("status"); // The client can invoke.
            f1.wheretoCall = null; // The delegate can be modified.
            f1.wheretoCall += CallHereToWriteInternally;
            f1.Search();
        }

    下面使用event关键字封装delegate,

        public class SearchFile
        {
            public delegate void WheretoCall(string status);    // Step1
    
            public event WheretoCall wheretoCall = null;      // Step 2
    
            public void Search()
            {
                // File search is happening
                for (int i = 0; i < 100; i++)
                {
                    string str = "File" + i;
                    wheretoCall(str);       // Step 3
                }
            }
        }

    编译客户端代码,出现如下错误:

    这个错误的意思是对于一个Event事件你只能订阅(+=)或者取消订阅(-=)。

    总结:

  • 相关阅读:
    HashMap、HashTable与ConcurrentHashMap的区别
    HashMap的扩容机制---resize()
    jdk7中hashmap实现原理和jdk8中hashmap的改进方法总结
    HTML5学习总结——canvas绘制象棋(canvas绘图)
    ShardingJdbc:水平切分
    ShardingSphere:基本概念
    KubeSphere:harbor+gitlab+nexus+springboot流水线部署
    k8s+jenkins:部署SpringCloud微服务
    k8s实现Jenkins的Master-Slave分布式构建
    windows下jar包转成exe执行文件
  • 原文地址:https://www.cnblogs.com/yang-fei/p/7444174.html
Copyright © 2011-2022 走看看