zoukankan      html  css  js  c++  java
  • C# 委托总结

    一、委托

    委托的本质:

    委托是一种特殊的数据类型,它表示某种特定类型的函数,并且可以表示多个函数,将这些函数串联起来。使用委托就好像函数调用一样。

    委托实质上是一个类,编译器会根据关键字delegate自动生成一个从System.Delegate类派生的类。所以,它具有可访问性,public, private等,也包含几个默认的成员函数和属性。(这些可通过IL代码看出编译器为委托生成的具体的类名称和代码)

    委托的作用:

    委托时一种在C#中实现函数动态调用的方式,通过委托可以将一些相同类型的函数串联起来依次执行。委托同时还是函数回调事件机制的基础。

    在函数调用时,委托链上可以具有相同的函数,只要通过“+=”操作添加到委托链上即可。

    委托的定义:

    delegate return_type DelegateName(Type1 para1, Type2 para2, ... ,[TypeN paraN]);

    delegate float DFloatFunc(int val1, float val2); // 定义一个委托类型

    泛型委托

    如果你知道泛型,那么就很容易理解泛型委托,说白了就是含有泛型参数的委托,例如:

    public delegate T Calculator<T> (T arg);

    我们可以把前面的例子改成泛型的例子,如下:

    public delegate T Calculator<T>(T arg);
    
    class Program {
    
        static int Double(int x) { return x * 2; }
        static void Main(string[] args) {
            int[] values = { 1, 2, 3, 4 };
            Utility.Calculate(values, Double);
    
            foreach (int i in values)
                Console.Write(i + " "); // 2 4 6 8
    
            Console.ReadKey();
        }
    }
    
    class Utility {
        public static void Calculate<T>(T[] values, Calculator<T> c) {
            for (int i = 0; i < values.Length; i++)
                values[i] = c(values[i]);
        }
    }

    Func 和 Action 委托

    有了泛型委托,就有了一能适用于任何返回类型和任意参数(类型和合理的个数)的通用委托,Func 和 Action。如下所示(下面的in表示参数,out表示返回结果):

    delegate TResult Func <out TResult> ();
    delegate TResult Func <in T, out TResult> (T arg);
    delegate TResult Func <in T1, in T2, out TResult> (T1 arg1, T2 arg2);
    ... 一直到 T16
    
    delegate void Action ();
    delegate void Action <in T> (T arg);
    delegate void Action <in T1, in T2> (T1 arg1, T2 arg2);
    ... 一直到 T16

    有了这样的通用委托,我们上面的Calculator泛型委托就可以删掉了,示例就可以更简洁了:

    public static void Calculate<T>(T[] values, Func<T,T> c) {
        for (int i = 0; i < values.Length; i++)
            values[i] = c(values[i]);
    }

    Func 和 Action 委托,除了ref参数和out参数,基本上能适用于任何泛型委托的场景,非常好用。

    参数类型兼容

    在OOP中,任何使用父类的地方均可以用子类代替,这个OOP思想对委托的参数同样有效。如:

    delegate void StringAction(string s);
    class Program {
        static void Main() {
            StringAction sa = new StringAction(ActOnObject);
            sa("hello");
        }
        static void ActOnObject(object o) {
            Console.WriteLine(o); // hello
    
        }
    }
    

    二、事件理论基础

    当我们使用委托场景时,我们很希望有这样两个角色出现:广播者和订阅者。我们需要这两个角色来实现订阅和广播这种很常见的场景。

    广播者这个角色应该有这样的功能:包括一个委托字段,通过调用委托来发出广播。而订阅者应该有这样的功能:可以通过调用 += 和 -= 来决定何时开始或停止订阅。

    事件就是描述这种场景模式的一个词。事件是委托的一个子集,为了满足“广播/订阅”模式的需求而生。

    事件建立在委托机制之上,通过该机制,某个类在发生某些特定的事情之后,通知其它类或对象正在发生的事情。

    事件(委托)

    从本质上来说,事件其实就是委托,但是它通常是特定类型的的函数类型,具有以下特点:

    事件发行者(类)确定何时引发事件,事件订阅者确定如何响应该事件。

    一个事件可以有多个订阅者。一个订阅者可以处理来自多个发行者的多个事件。

    没有订阅者的事件,永远不会被调用。

    如果一个事件有多个订阅户,当引发该事件时,会同步调用多个事件处理程序。

    在.NET类库中,事件是基于EventHandle委托和EventArgs基类的。

    事件响应函数委托

    其通常没有返回值,有sender 和 arg两个参数。在定义一个事件之前,要先定义事件的参数类型,该类型包含了事件发起者需要提供给事件订阅者的信息。

    引发事件

    实际上就是调用委托变量。但是在事件被定义之后,该变量默认为null, 直接引发会产生异常,所以调用之前要判断事件是否为null(是否已经被订阅)。通常通过定义OnXXX()的函数来引发XXX事件,在该函数中首先判断事件是否被订阅,如果被订阅则引发该事件。

    订阅和处理事件

    此方面的一个核心元素是事件响应函数。事件响应函数是符合呀哦订阅的事件委托类型的函数,它通常根据事件的引发者和参数进行相应的处理。

    由于事件的本质是委托,所以事件的订阅实际上通过“+=”运算将当前类的事件响应函数添加到时间段额委托链中,在引发事件时就可以调用该处理函数。

    委托的使用

     声明一个事件

    声明一个事件很简单,只需在声明一个委托对象时加上event关键字就行。如下:

    public delegate void PriceChangedHandler (decimal oldPrice, decimal newPrice);
    public class IPhone6
    {
        public event PriceChangedHandler PriceChanged;
    }

     事件的使用和委托完全一样,只是多了些约束。下面是一个简单的事件使用例子:

    public delegate void PriceChangedHandler(decimal oldPrice, decimal newPrice);
    
    public class IPhone6 {
        decimal price;
        public event PriceChangedHandler PriceChanged;
        public decimal Price {
            get { return price; }
            set {
                if (price == value) return;
                decimal oldPrice = price;
                price = value;
                // 如果调用列表不为空,则触发。
                if (PriceChanged != null)
                    PriceChanged(oldPrice, price);
            }
        }
    }
    
    class Program {
        static void Main() {
            IPhone6 iphone6 = new IPhone6() { Price = 5288 };
            // 订阅事件
            iphone6.PriceChanged += iphone6_PriceChanged;
    
            // 调整价格(事件发生)
            iphone6.Price = 3999;
    
            Console.ReadKey();
        }
    
        static void iphone6_PriceChanged(decimal oldPrice, decimal price) {
            Console.WriteLine("年终大促销,iPhone 6 只卖 " + price + " 元, 原价 " + oldPrice + " 元,快来抢!");
        }
    }

    有人可能会问,如果把上面的event关键字拿掉,结果不是一样的吗,到底有何不同?

    没错可以用事件的地方就一定可以用委托代替。

    但事件有一系列规则和约束用以保证程序的安全可控,事件只有 += 和 -= 操作,这样订阅者只能有订阅或取消订阅操作,没有权限执行其它操作。如果是委托,那么订阅者就可以使用 = 来对委托对象重新赋值(其它订阅者全部被取消订阅),甚至将其设置为null,甚至订阅者还可以直接调用委托,这些都是很危险的操作,广播者就失去了独享控制权。

    事件保证了程序的安全性和健壮性。

    参考资料

    [ASP.NET MVC 大牛之路]02 - C#高级知识点概要(1) - 委托和事件

  • 相关阅读:
    Spring-Boot:多种配置注入方式
    Spring-Boot:Profile简单示例
    Spring-Boot:拦截器注解范例
    Docker:镜像的迁移
    YARN的内存和CPU配置
    Spark On YARN内存分配
    Spark配置参数
    linux_密钥
    分布式架构高可用架构篇_04_Keepalived+Nginx实现高可用Web负载均衡
    PythonDay01
  • 原文地址:https://www.cnblogs.com/arxive/p/5973200.html
Copyright © 2011-2022 走看看