来自 威尼斯国际官方网站 2019-09-21 02:27 的文章
当前位置: 威尼斯国际官方网站 > 威尼斯国际官方网站 > 正文

委托和事件

<委托类型> <实例化名>=new <委托类型>(<注册函数>)

这样就定义了一个委托,但是委托在.net内相当于声明了一个类(在后面的代码中会讲到确实如此),类如果不实例化为对象,很多功能是没有办法使用的,委托也是如此.

委托给了C#操作函数的灵活性,我们可使用委托像操作变量一样来操作函数,其实这个功能并不是C#的首创,早在C++时代就有函数指针这一说法,而在我看来委托就是C#的函数指针,首先先简要的介绍一下委托的基本知识:

委托实例化的原型是

例子:public delegate void CheckDelegate(int number);//定义了一个委托CheckDelegate,它可以注册返回void类型且有一个int作为参数的函数

例子:CheckDelegate _checkDelegate=CheckMod;//用函数CheckMod实例化上面的CheckDelegate 委托为_checkDelegate
现在我们就可以像使用函数一样来使用委托了,在上面的例子中现在执行_checkDelegate()就等同于执行CheckMod(),最关键的是现在函数CheckMod相当于放在了变量当中,它可以传递给其它的CheckDelegate引用对象,而且可以作为函数参数传递到其他函数内,也可以作为函数的返回类型

include <stdio.h>

void square(int x) { printf("square of %d is %dn",x,xx); }
void cube(int x) { printf("cube of %d is %dn",x,x
xx); }
int main()
{
void (
calcu)(int x);
calcu=square;
calcu();
return ;
}

二.C#中委托的实质

  委托又名委托类型,为什么C#弄出这个东西?因为C#是一门比较安全的语言,不允许操作指针,于是我们不能定义函数指针。但想要达到相同的效果,于是定义了委托类型。所谓委托类型,其本质就是C中的指针类型。于是代码变成了这样:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Delegate
{
class Program
{
static void square(int x) { Console.WriteLine("square of {} is {}", x, x * x); }
static void cube(int x) { Console.WriteLine("cube of {} is {}", x, x * x * x); }
delegate void math(int x); //定义委托类型
static void Main(string[] args)
{
math calcu;
calcu += square;
calcu();
Console.ReadKey();
}
}
}

  可以看出,定义委托类型math实际上就相当于定义了void*类型。而委托类型实例化得到的calcu实际上就是函数指针。(说句题外话:定义函数(方法)时要加上static是因为调用函数时并未实例化,只有静态方法能够直接通过类调用)。

三.委托的使用方法

  我们在上述代码19行后面加上一行代码 calcu+=cube; 运行会发现,square和cube均被调用。可以看出,符号 += 表示绑定方法到委托变量,同理符号 -= 表示取消绑定。可以理解为calcu是void **类型,即它指向了一个数组,数组中的每一项都是函数指针类型,每次调用calcu时,遍历此数组,即依次调用每个绑定的方法。

四.封装与事件的引入

  下面我们要用面向对象的思想将上述代码进行封装,使其变清晰。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Delegate
{
public delegate void math(int x);
public class Calcu
{
public math calcu;
}
class Program
{
static void square(int x) { Console.WriteLine("square of {} is {}", x, x * x); }
static void cube(int x) { Console.WriteLine("cube of {} is {}", x, x * x * x); }
static void Main(string[] args)
{
Calcu c = new Calcu();
c.calcu += square;
c.calcu += cube;
c.calcu();
Console.ReadKey();
}
}
}

由于委托变量是public的,封装的程度很低,在外部可以任意修改。为了改进这个问题,C#引入了事件。

  所谓事件,实际上还是委托的实例化,只是其内部多了一些定义,多了一些限制。其一,事件实际上声明了一个private类型的委托变量,因此在类外无法直接调用。

  于是我们将上述代码的第12行改成这样:

public event math calcu;

威尼斯国际官方网站 ,  运行之后25行报错了,因为calcu是private的,不能直接调用。但23,24行并没有报错。那么问题来了,为什么我们可以用+=来给calcu绑定方法呢?

  因为其二,事件还帮我们干了一件事情,就是定义了绑定方法和取消绑定方法的函数,它们是public的,并且将运算符+=,-=重载,和这两个函数对应。

  好了,现在我们要写一个接口函数来完成计算:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Delegate
{
public delegate void math(int x);
public class Calcu
{
public event math calcu;
public void calculate(int x)
{
calcu(x);
}
}
class Program
{
static void square(int x) { Console.WriteLine("square of {} is {}", x, x * x); }
static void cube(int x) { Console.WriteLine("cube of {} is {}", x, x * x * x); }
static void Main(string[] args)
{
Calcu c = new Calcu();
c.calcu += square;
c.calcu += cube;
c.calculate();
Console.ReadKey();
}
}
}


详解C#委托,事件与回调函数

.Net编程中最经常用的元素,事件必然是其中之一。无论在ASP.NET还是WINFrom开发中,窗体加载(Load),绘制(Paint),初始化(Init)等等。
“protected void Page_Load(object sender, EventArgs e)”这段代码相信没有人不熟悉的。细心一点一定会发现,非常多的事件方法都是带了“object sender, EventArgs e”这两个参数。这是不是和委托非常相似呢?

一、委托(有些书中也称为委派)

委托是什么呢?这个名字的意思已经赋予了我们想象的空间,你是编程的,你现在正在写一个ASP.NET网页,而JS是你不熟悉的,于是你委托你的一位同事来帮助你完成JS部分。这就是委托,把你所不能做的事情交给其他人去做。而怎么知道是哪个人去做呢?当然是要知道名字!而为了区别名字一样的不同人,因此,需要描述一个特征。

在C#中,委托的作用是这样描述的:委托就像一个函数的指针,在程序运行时可以使用它们来调用不同的函数。这个其实和你委托同事完成 JS代码一样。如果有两位同事可以做这件事情,他们只要做的结果能够满足你的需求(就像一个接口),尽管他们做的过程不一样,并且作出的效果也不一样,但是,能够达到你的要求就可以了。

1、简单的委托

那委托需要承载哪些信息呢?首先,它存储了方法名,还有参数列表(方法签名),以及返回的类型。比如:
delegate string/返回类型/ ProcessDelegate(int i);
这就是一个委托的定义。蓝色部分是声明委托的关键字,红色部分是返回的类型,而黑色部分是委托的类型名,和一个类名差不多,而()里的就是参数部分。它的意思是,你要使用这个委托来做事情的话,那么,做事情的方法必须满足以下条件:
1、返回类型和委托的返回类型一致,这里是string类型;
2、能且只能有一个参数,并且是int类型。
OK,满足以上两个条件,一切就可以工作了:)

例如:

using System;
using System.Collections.Generic;
using System.Text;

namespace TestApp
{
///

/// 委托
///
///
///
///
public delegate string ProcessDelegate(string s1, string s2);

 class Program
 {
     static void Main(string[] args)
     {
         /*  调用方法  */
         ProcessDelegate pd = new ProcessDelegate(new Test().Process);
         Console.WriteLine(pd("Text1", "Text2"));
     }
 }

 public class Test
 {
     /// <summary>
     /// 方法
     /// </summary>
     /// <param name="s1"></param>
     /// <param name="s2"></param>
     /// <returns></returns>
     public string Process(string s1,string s2)
     {
         return s1 + s2;
     }
 }

}
输出的结果是:
Text1Tex2

2、泛型委托

泛型的委托,就是然参数的类型不确定,例如代码改写为:

using System;
using System.Collections.Generic;
using System.Text;

namespace TestApp
{
///

/// 委托
///
///
///
///
public delegate string ProcessDelegate<T,S>(T s1, S s2);

class Program
{
    static void Main(string[] args)
    {
        /*  调用方法  */
        ProcessDelegate<string,int> pd = new ProcessDelegate<string,int>(new Test().Process);
        Console.WriteLine(pd("Text1", 100));
    }
}

public class Test
{
    /// <summary>
    /// 方法
    /// </summary>
    /// <param name="s1"></param>
    /// <param name="s2"></param>
    /// <returns></returns>
    public string Process(string s1,int s2)
    {
        return s1 + s2;
    }
}

}

输出的结果就是:
Text1100

泛型的详细内容不属于本文的介绍范围,这里不加多说了。

二、事件

在某件事情发生时,一个对象可以通过事件通知另一个对象。比如,前台完成了前台界面,他通知你,可以把前台和你开发的程序整合了。这就是一个事件。可以看出事件是在一个时间节点去触发另外一件事情,而另外一件事情怎么去做,他不会关心。就事件来说,关键点就是什么时候,让谁去做。

在C#中,时间定义关键字是event。例如:
event ProcessDelegate ProcessEvent;

整个事件定义方法以及执行过程:

using System;
using System.Collections.Generic;
using System.Text;

namespace TestApp
{
///

/// 委托
///
///
///
///
public delegate void ProcessDelegate(object sender, EventArgs e);

class Program
{


    static void Main(string[] args)
    {
        /*  第一步执行  */
        Test t = new Test();
        /* 关联事件方法,相当于寻找到了委托人 */
        t.ProcessEvent += new ProcessDelegate(t_ProcessEvent);
        /* 进入Process方法 */
        Console.WriteLine(t.Process()); 

        Console.Read();
    }

    static void t_ProcessEvent(object sender, EventArgs e)
    {
        Test t = (Test)sender;
        t.Text1 = "Hello";
        t.Text2 = "World";
    }
}

public class Test
{
    private string s1;

    public string Text1
    {
        get { return s1; }
        set { s1 = value; }
    }

    private string s2;

    public string Text2
    {
        get { return s2; }
        set { s2 = value; }
    }


    public event ProcessDelegate ProcessEvent;

    void ProcessAction(object sender, EventArgs e)
    {
        if (ProcessEvent == null)
            ProcessEvent += new ProcessDelegate(t_ProcessEvent);
        ProcessEvent(sender, e);
    }

    //如果没有自己指定关联方法,将会调用该方法抛出错误
    void t_ProcessEvent(object sender, EventArgs e)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    void OnProcess()
    {
        ProcessAction(this, EventArgs.Empty);
    }

    public string Process()
    {
        OnProcess();
        return s1 + s2;
    }
}

}

感觉到了什么?是不是和代码注入了差不多,相当于是可以用任意符合委托接口(委托确实很像接口)的代码,注入到Process过程。在他返回之前给他赋值。

三、回调函数

打了这么多字,好累啊!

回调函数就是把一个方法的传给另外一个方法去执行。在C#有很多回调函数,比如异步操作的时候。这里先举个例子:

using System;
using System.Collections.Generic;
using System.Text;

namespace TestApp
{
///

/// 委托
///
///
///
///
public delegate string ProcessDelegate(string s1, string s2);

class Program
{
    static void Main(string[] args)
    {
        /*  调用方法  */
        Test t = new Test();
        string r1 = t.Process("Text1", "Text2", new ProcessDelegate(t.Process1));
        string r2 = t.Process("Text1", "Text2", new ProcessDelegate(t.Process2));
        string r3 = t.Process("Text1", "Text2", new ProcessDelegate(t.Process3));

        Console.WriteLine(r1);
        Console.WriteLine(r2);
        Console.WriteLine(r3);
    }
}

public class Test
{
    public string Process(string s1,string s2,ProcessDelegate process)
    {
        return process(s1, s2);
    }

    public string Process1(string s1, string s2)
    {
        return s1 + s2;
    }

    public string Process2(string s1, string s2)
    {
        return s1 + Environment.NewLine + s2;
    }

    public string Process3(string s1, string s2)
    {
        return s2 + s1;
    }
}

}

输出结果:
Text1Text2
Text1
Text2
Text2Text1

Process方法调用了一个回调函数,当然这里只执行了回调函数。可以看出,可以把任意一个符合这个委托的方法传递进去,意思就是说这部分代码是可变的。而设计上有一个抽离出可变部分代码的原则,这种用法无疑可以用到那种场合了。


委托给了C#操作函数的灵活性,我们可使用委托像操作变量一样来操作函数,其实这个功能并不是C#的首创,早在C++时代就有函数指针这一说法,而在我看来委托就是C#的函数指针,首先先简要的介绍一下委托的基本知识:

委托的定义
委托的声明原型是
delegate <函数返回类型> <委托名> (<函数参数>)
例子:public delegate void CheckDelegate(int number);//定义了一个委托CheckDelegate,它可以注册返回void类型且有一个int作为参数的函数
这样就定义了一个委托,但是委托在.net内相当于声明了一个类(在后面的代码中会讲到确实如此),类如果不实例化为对象,很多功能是没有办法使用的,委托也是如此.

委托的实例化
委托实例化的原型是
<委托类型> <实例化名>=new <委托类型>(<注册函数>)
例子:CheckDelegate _checkDelegate=new CheckDelegate(CheckMod);//用函数CheckMod实例化上面的CheckDelegate 委托为_checkDelegate
在.net 2.0开始可以直接用匹配的函数实例化委托:
<委托类型> <实例化名>=<注册函数>
例子:CheckDelegate _checkDelegate=CheckMod;//用函数CheckMod实例化上面的CheckDelegate 委托为_checkDelegate
现在我们就可以像使用函数一样来使用委托了,在上面的例子中现在执行_checkDelegate()就等同于执行CheckMod(),最关键的是现在函数CheckMod相当于放在了变量当中,它可以传递给其它的CheckDelegate引用对象,而且可以作为函数参数传递到其他函数内,也可以作为函数的返回类型

用匿名函数初始化委托

上面为了初始化委托要定义一个函数是不是感觉有点麻烦,另外被赋予委托的函数一般都是通过委托实例来调用,很少会直接调用函数本身。

在.net 2.0的时候考虑到这种情况,于是匿名函数就诞生了,由于匿名函数没有名字所以必须要用一个委托实例来引用它,定义匿名函数就是为了初始化委托

匿名函数初始化委托的原型:

<委托类型> <实例化名>=new <委托类型>(delegate(<函数参数>){函数体});

当然在.net 2.0后可以用:

<委托类型> <实例化名>=delegate(<函数参数>){函数体};

例子:

    delegate void Func1(int i);
    delegate int Func2(int i);

    static Func1 t1 =new Func1(delegate(int i)
    {
        Console.WriteLine(i);
    });

    static Func2 t2;

    static void Main(string[] args)
    {
        t2 = delegate(int j)
        {
            return j;
        };
        t1(2);

        Console.WriteLine(t2(1));

    }

当然在.net 3.0的时候又有了比匿名函数更方便的东西lambda表达式,这儿就不说了。

泛型委托
委托也支持泛型的使用
泛型委托原型:
delegate

static int test(int t)
{
return t;
}

static void Main(string[] args)
{
A<int, int> a =test;//将泛型委托委托<T1,T2>实例化为<int,int>,即表示有一个int类型参数且返回类型是int的函数,所以将test用来实例化委托
Console.WriteLine(a(5));//输出5
}

委托的多播性
在上面实例化委托的时候看到:必须将一个匹配函数注册到委托上来实例化一个委托对象,但是一个实例化委托不仅可以注册一个函数还可以注册多个函数,注册多个函数后,在执行委托的时候会根据注册函数的注册先后顺序依次执行每一个注册函数
函数注册委托的原型:
<委托类型> <实例化名>+=new <委托类型>(<注册函数>)
例子:CheckDelegate _checkDelegate=new CheckDelegate(CheckMod);//将函数CheckMod注册到委托实例_checkDelegate上
在.net 2.0开始可以直接将匹配的函数注册到实例化委托:
<委托类型> <实例化名>+=<注册函数>
例子:CheckDelegate _checkDelegate+=CheckMod;//将函数CheckMod注册到委托实例_checkDelegate上
之后我们还可以注册多个函数到委托上:
例子:_checkDelegate+=CheckPositive;//将函数CheckPositive注册到委托实例_checkDelegate上
_checkDelegate();//执行这个委托实例会先执行CheckMod()再执行CheckPositive()

实际上使用+=符号的时候会判断
如果此时委托还没有实例化(委托实例为null),它会自动用+=右边的函数实例化委托
如果此时委托已经实例化,它会只把+=右边的函数注册到委托实例上
另外有一点需要注意的是,如果对注册了函数的委托实例从新使用=号赋值,相当于是重新实例化了委托,之前在上面注册的函数和委托实例之间也不再产生任何关系,后面的例子会讲到这点!

当然有+=注册函数到委托,也有-=解除注册
例子:_checkDelegate-=new CheckDelegate(CheckPositive);//解除CheckPositive对_checkDelegate的注册
_checkDelegate-=CheckPositive;//.net 2.0开始可以用这种方式解除注册

另外当在委托和事件(事件的细节将在后面介绍)上注册了多个函数后,如果委托和事件有返回值,那么调用委托和事件时,返回的将是最后一个注册函数的返回值。如下示例代码将做详细解释。

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

namespace MultiDelegatesReturn
{
public delegate int DelMath(int i);//定义委托类DelMath,该委托传入一个int类型参数,返回一个int类型参数

class Program
{
    static DelMath dMath;//通过委托类型DelMath定义委托实例dMath
    static event DelMath eMath;//通过委托类型DelMath定义事件实例eMath

    /// <summary>
    /// 将传入的参数i自加后作为函数返回值
    /// </summary>
    static int IncMath(int i)
    {
        i++;
        Console.WriteLine("IncMath has been invoked!");
        return i;
    }

    /// <summary>
    /// 将传入的参数i自减后作为函数返回值
    /// </summary>
    static int DecMath(int i)
    {
        i--;
        Console.WriteLine("DecMath has been invoked!");
        return i;
    }

    static void Main(string[] args)
    {
        int i = 10;//定义int型变量i,初始值为10

        dMath += IncMath;//先将IncMath函数注册到委托实例dMath
        dMath += DecMath;//再将DecMath函数注册到委托实例dMath

        Console.WriteLine("dMath returned:" + dMath(i).ToString());//将int型变量10传入委托实例dMath调用后,返回的结果是9,说明委托实例
        //dMath返回的是后注册的函数DecMath的返回值

        eMath += IncMath;//先将IncMath函数注册到事件实例eMath
        eMath += DecMath;//再将DecMath函数注册到事件实例eMath

        Console.WriteLine("eMath returned:" + eMath(i).ToString());//将int型变量10传入事件实例eMath调用后,返回的结果也是9,说明事件实例
        //eMath返回的也是后注册的函数DecMath的返回值

    }
}

}

c#事件:
了解委托之后,就可以来谈谈事件了,C#事件是什么?
c#事件的定义和委托的声明是如此的相似:
event <委托类型> 事件名
例子:public event CheckDelegate checkEvent;
上面的例子声明了个事件叫checkEvent你会发现它只比声明委托实例前多了个关键字event
声明了事件后就可以实例化事件,注册函数到事件,解除事件函数注册其方法和委托的步骤如出一辙:
例子:checkEvent+=new CheckDelegate(CheckMod);//将函数CheckMod注册到事件checkEvent上
checkEvent+=CheckMod;//.net 2.0开始支持这种方法
checkEvent-=new CheckDelegate(CheckMod);//将函数CheckMod解除对事件checkEvent的注册
checkEvent-=CheckMod;//.net 2.0开始支持这种方法

从种种迹象都可以看出事件和委托实例是那么的相似,那么为什么不直接用委托还要用到事件呢?其实事件就是对委托的封装,就如同c#类中属性对字段的封装一样,其封装后可以在委托上封装更复杂的逻辑,下面我们来看c#中事件的两种声明方式,来了解事件对委托的封装

隐式声明事件
这种方式声明事件很简单,就如同声明委托实例一样:
event <委托类型> 事件名;
例子:public event CheckDelegate checkEvent;
我们用反射机制来看看这样声明的事件里面装的到底是什么东西

我们可以看到在事件被编译后自动生成了个private的委托实例checkEvent和两个函数add_checkEvent和remove_checkEvent,这两个函数分别对应事件的+=/-=操作,另外可以看到在声明了事件后的确是产生了一个和事件同名私有的委托实例checkEvent,对事件的+=/-=操作都会反映在这个同名委托实例checkEvent上,所以可以在定义事件的类里面直接调用checkEvent()来执行注册函数和对checkEvent使用=号重新赋值,实际上这里操作的并不是checkEvent事件,而操作的是同名委托实例checkEvent,因此隐式声明的事件,其实就是由一个委托实例和两个函数封装而成,所有的操作最终都反映在委托实例上。

(这里我补充下我的个人理解:事实上在一个类的内部是无法定义一个事件后又定义一个和事件同名的委托实例的,如果你在本例中尝试再定义CheckDelegate checkEvent,编译的时候会报错并提示已经定义了名叫checkEvent的委托,原因是因为事件本来就是一种特殊的委托实例(不管是隐式或显式声明的事件都是这样),因此定义和事件同名的委托实例会报错,所以我个人认为.net在编译的时候会把隐式声明的事件编译成为委托实例(和事件同名),本例中的checkEvent事件在编译后也不再是事件转而被编译成了checkEvent委托实例,否则又怎么可能在定义事件的类的内部可以执行事件和对事件赋值呢(这里大家可以看看我给的显式声明事件的例子,那里面有说到),唯一的解释就是隐式声明的事件其实就是委托实例)

显式声明事件
其实显示声明事件就是要自己来手动实现隐式声明事件的一个委托实例
和两个函数:
event <委托类型> 事件名
{
add
{
//将函数注册到自己定义的委托实例
}

  remove
  {
        //解除函数对自己定义的委托实例的注册
  }

}

例子:private CheckDelegate _checkDelete;
public event CheckDelegate checkEvent
{
add
{
_checkDelete = Delegate.Combine(_checkDelete, value) as CheckDelegate;
}
remove
{
_checkDelete = Delegate.Remove(_checkDelete, value) as CheckDelegate;
}
}
//Delegate.Combine和Delegate.Remove是.net库函数,作用是合并委托实例注册函数和移除委托实例注册函数并返回合并和移除后的委托实例,具体解释请查阅MSDN

我们再用反射机制查看显式声明事件编译后的代码

可以看到显示声明事件的代码编译后和隐式声明事件的代码几乎相同,只不过这里我们自己定义了事件操作委托实例_checkDelete。另外显式声明的事件不支持直接调用,就算在定义事件的类里面也不能直接调用显式声明的事件(checkEvent();//这样会报错),应该调用事件委托实例(_checkDelete();)。

本文例子
俗话说得好说得多不如做得多,现在就把例子发出来,例子中还讲了些东西,可以执行例子看了输出结果后再体会:
首先是个c#类库项目ClassLibrary,里面包含两个类分别是显式声明和隐式声明事件
AutoCheckClass.cs

using System;
using System.Collections.Generic;
using System.Text;

namespace ClassLibrary
{
public class AutoCheckClass
{
public delegate void CheckDelegate(int number);
public event CheckDelegate checkEvent;

    public void WriteInner(int n)
    {
        Console.WriteLine(n.ToString());
    }

    public void InitEvent()
    {
        checkEvent = WriteInner;//对事件从新赋值
        //checkEvent = new CheckDelegate(WriteInner);//也可以用委托对事件进行赋值
    }

    public void Exec(int n)
    {
        checkEvent(n);
    }

    /*
     采用这种方式,public event CheckDelegate checkEvent;会自动生成一个private CheckDelegate checkEvent,
     对于public event CheckDelegate checkEvent;的+/-操作都会在编译时反应在private CheckDelegate checkEvent上
     而且add/remove .net在编译的时候会自动生成,不用自己再操心,缺点是每个事件的委托都被封装,无法操作其内部的委托

     此外采用这种方式定义的事件,可以在定义事件的类的内部直接对事件进行赋值,例如可以在Exec函数中加上下面这句代码:
     checkEvent = Exec;
     表示该事件可以被匹配的函数或委托赋值初始化。
     并且对事件进行赋值操作,相当于从新初始化事件内部的委托(同名委托实例),会让赋值之前对事件注册的函数都不再与事件产生关系,具体示例请见本类中InitEvent函数的使用效果。
     */
}

}

CheckClass.cs

using System;
using System.Collections.Generic;
using System.Text;

namespace ClassLibrary
{
public class CheckClass
{
public delegate void CheckDelegate(int number);
private CheckDelegate _checkDelete;
public event CheckDelegate checkEvent
{
add
{
_checkDelete = Delegate.Combine(_checkDelete, value) as CheckDelegate;
}
remove
{
_checkDelete = Delegate.Remove(_checkDelete, value) as CheckDelegate;
}
}

    public void Exec(int n)
    {
        _checkDelete(n);
        //checkEvent = Exec;注意显示定义事件的方式,不支持对事件直接进行赋值
    }

    /*
     delegate在编译的时候会被net编译成一个类,如下:
     public delegate void CheckDelegate(int number);在编译的时候会编译为下面的类
     public sealed class CheckDelegate:System.MulticastDelegate
     {
        public GreetingDelegate(object @object, IntPtr method);
        public virtual IAsyncResult BeginInvoke(string name, AsyncCallback callback, object @object);
        public virtual void EndInvoke(IAsyncResult result);
        public virtual void Invoke(string name);
     }
     而System.MulticastDelegate继承于System.Delegate,所以下面的代码才会顺利执行
     _checkDelete = Delegate.Combine(_checkDelete, value) as CheckDelegate;
     _checkDelete = Delegate.Remove(_checkDelete, value) as CheckDelegate; 
     采用这种方法可以让你自己指定事件的委托,甚至可以让多个事件使用同一个委托,且自己实现add/remove,可以实现更复杂的逻辑

     此外需要注意的是,采用这种方式定义的事件,就算在定义事件的类的内部都无法对事件直接进行赋值,例如先前在另外种定义方式说到的在Exec函数中加上:
     checkEvent = Exec;
     会报错:事件“ClassLibrary.CheckClass.checkEvent”只能出现在 += 或 -= 的左边
     所以在这里我们不应该操作checkEvent,因为它没有同名委托实例,而因该操作_checkDelete
     */
}

}

然后是个控制台项目,需要引入上面的类库的dll文件
Program.cs

sing System;
using System.Collections.Generic;
using System.Text;
using ClassLibrary;

namespace DeleGate
{
class Temp//定义此类是为了在代码中展示函数对委托和事件的另外一种注册方式
{
public delegate void TempDelegate(int u);
public static TempDelegate td;
public static event TempDelegate ed;
}

class Program
{
    private static void CheckMod(int number)
    {
        if (number % 2 == 0)
            Console.WriteLine("输入的是偶数");
        else
            Console.WriteLine("输入的不是偶数");
    }

    private static void CheckPositive(int number)
    {
        if (number > 0)
            Console.WriteLine("输入的是正数");
        else
            Console.WriteLine("输入的不是正数");
    }


    static void Main(string[] args)
    {
        CheckClass cc = new CheckClass();
        cc.checkEvent += new CheckClass.CheckDelegate(CheckMod);
        cc.checkEvent += new CheckClass.CheckDelegate(CheckPositive);

        AutoCheckClass acc = new AutoCheckClass();
        acc.checkEvent += new AutoCheckClass.CheckDelegate(CheckMod);
        acc.checkEvent += new AutoCheckClass.CheckDelegate(CheckPositive);
        //acc.InitEvent();//执行了这个方法后,由于对事件从新赋了值,上面对事件注册的两个函数都会失效

        Temp.td = CheckMod;//这表示对委托进行赋值(等同于:Temp.td = new Temp.TempDelegate(CheckMod);),和对事件赋值一样,对委托进行赋值相当于初始化委托,会让赋值之前在委托上注册的函数与委托失去注册关系。
        Temp.td += CheckPositive;
        Console.WriteLine("Temp的结果");
        Temp.td(50);

        Temp.ed += CheckMod;
        Temp.ed += CheckPositive;

        Console.WriteLine("cc的结果");
        cc.Exec(50);
        Console.WriteLine("acc的结果");
        acc.Exec(50);


        Console.ReadKey();
    }
}

}

附加更新补充

调用委托实例的对象并不是调用委托函数的对象

通过前面的例子,我们了解到了,委托其实就是C#中的函数指针,有了委托我们可以像使用变量一样来使用函数。但是请切记调用委托实例的对象,绝不是调用委托函数的对象。这一点我们通过如下例子来说明.

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

namespace DelagateInstanceCall
{
//DelegateContainer是定义委托类型DelMethod和委托实例delMethod的类
class DelegateContainer
{
public delegate void DelMethod();//定义一个无参数且无返回值的委托类型DelMethod

    public DelMethod delMethod;//定义委托类型DelMethod的委托实例delMethod

    public int i = 100;//定义一个int类型的变量i在类DelegateContainer之中,赋值100
}

//MethodDemo是定义委托函数DisplayMethod的类
class MethodDemo
{
    protected int i = 200;//定义一个int类型的变量i在类MethodDemo之中,赋值200

    //定义委托函数DisplayMethod
    public void DisplayMethod()
    {
        Console.WriteLine("Varible i is : " + this.i.ToString());//显示变量i的值,通过这里的值就可以知道委托函数DisplayMethod的调用对象是谁
    }
}


class Program
{
    static void Main(string[] args)
    {
        DelegateContainer delCon = new DelegateContainer();//构造类DelegateContainer的对象delCon
        MethodDemo metDemo = new MethodDemo();//构造类MethodDemo的对象metDemo

        delCon.delMethod += metDemo.DisplayMethod;//将函数DisplayMethod注册到委托实例delMethod,让其作为delMethod的委托函数

        delCon.delMethod();//调用委托实例delMethod的时候,就会调用在它上注册的委托函数DisplayMethod,那么在执行委托函数DisplayMethod时,其内部代码中的this,到底指的是
        //委托实例delMethod的调用对象delCon呢,还是委托函数DisplayMethod的调用对象metDemo呢?
        //我可以看到这里输出的结果是"Varible i is : 200",说明DisplayMethod内部的this指的是委托函数DisplayMethod本身的调用对象metDemo。这里大家很容易搞混淆,由于我们上面是通过
        //调用委托实例delCon.delMethod来调用委托函数metDemo.DisplayMethod的,看到delCon.delMethod()时大家潜意识可能就会认为由于调用委托实例delMethod的对象是delCon,就认为
        //调用委托实例delMethod上注册函数DisplayMethod的对象也是delCon,其实这是错误的。大家一定要记住委托实例只是一个壳子,它只是用来代表在其上注册的函数,但它并不会改变注册函数
        //的环境变量(比如函数的调用对象等),由于我们上面将委托函数DisplayMethod注册到委托实例delMethod时,使用的是delCon.delMethod += metDemo.DisplayMethod,所以函数的调用
        //对象始终都是等号右边的对象metDemo,而不会是左边的对象delCon,而调用等号左边的委托实例delCon.delMethod()时,相当于就是在执行等号右边的metDemo.DisplayMethod(),
        //所以委托函数DisplayMethod的调用对象始终是metDemo。

        //由此请大家一定要记住,调用委托实例的对象和调用委托函数的对象没有丝毫关系,要看委托函数是谁调用的,还得要看函数注册到委托实例时,等号右边注册函数前的调用对象是谁。

        Console.ReadKey();
    }
}

}

从上面这个例子,我们可以牢牢记住,调用委托实例的对象和调用委托函数的对象没有丝毫关系,要看委托函数是谁调用的,还得要看函数注册到委托实例时,等号右边注册函数前的调用对象是谁。这样在使用委托时就不会出错和弄混淆。

委托的定义

例子:CheckDelegate _checkDelegate=new CheckDelegate(CheckMod);//用函数CheckMod实例化上面的CheckDelegate 委托为_checkDelegate

委托的声明原型是

事件是委托的一种特殊形式,当发生有意义的事情时,事件处理对象通知过程。

委托的实例化

在.net 2.0开始可以直接用匹配的函数实例化委托:

<委托类型> <实例化名>=<注册函数>

以下都是我在网上收集下来的感觉是比较易懂,经典的。

.
.

一.C语言中的函数指针

  想要理解什么是委托,就要先理解函数指针的概念。所谓函数指针,就是指向函数的指针(等于没说-.-)。比如我定义了两个函数square和cube分别用于计算一个数的平方和立方,我再定义函数指针calcu,然后我让calcu指向square,那么调用calcu时就相当于调用了square函数(注意,此处函数指针接受的参数类型及个数要与函数一致)。很好理解吧?不多说,上代码。

C#委托与事件初探

作者:Koala''s_Dream 字体:[增加 减小] 类型:转载 时间:2016-02-14 我要评论

delegate <函数返回类型> <委托名> (<函数参数>)

事件是委托的一种特殊形式,当发生有意义的事情时,事件处理对象通知过程。接下来通过本文给大家介绍C#委托与事件初探,感兴趣的朋友一起学习吧

本文由威尼斯国际官方网站发布于威尼斯国际官方网站,转载请注明出处:委托和事件

关键词: