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) - 委托和事件

原文地址:https://www.cnblogs.com/arxive/p/5973200.html