Wu.Country@侠缘

勤学似春起之苗,不见其增,日有所长; 辍学如磨刀之石,不见其损,日所有亏!

对C#下函数,委托,事件的一点理解!

 

今天一来是有点空,二来是在博客上偶然看到有关于委托的文章,一时兴起,就自己也写一点心得与大家分享一下。

 

先看一个例子:

using System;
namespace ConsoleApplication1
{
    
class Class1
    
{
        [STAThread]
        
static void Main(string[] args)
        
{
            
bool m_isRight = false;
            
object m_obj = m_isRight?MyWrite("true"):MyWrite("false");
            Console.Write(m_obj);
        }

        
static private int MyWrite(object i_string)
        
{
            Console.Write(i_string);
            
return i_string.ToString().Length;
        }

    }

}

问输出的结果是什么?有一个刚学习程序设计不久的学生的回答是:false false

这个结果给我的映像很深,为什么呢?因为我觉得这个不仅仅是学生的一个错误,而更多的是这个学生深入的思考了问题。

因为m_obj是一个对象,所以这个学生理解为:MyWrite()这个函数对象可以直接赋值给m_obj,然后m_obj就当成MyWrite()这个函数来调用,所以他就认为:

Console.Write (m_obj); 等于是:Console.Write (MyWrite(“false”));
这是思维是很有创意的,不是吗?

于是就是C#里而很多人不好理解的委托了。其实,从使用上讲,它就是一个函数变量!如上面的例子,如果真的是想把MyWrite()做为对象赋值给m_obj会是个什么结果呢?

我觉得我们先得解决以下几个问题,才能正确的把函数当成变量赋值给一个对象:

1、             如果可以给一个对象赋函数值,如何来区别不同的函数?

2、             如何区别它是一个函数赋值,还是一个普通的对象赋值?

3、             如何用这个对象来调用原来的函数?

如果把这几个问题解决了,委托也就明白了一半。

先看问题1,如果可以给一个对象赋函数值,如何来区别不同的函数?

首先应该明白的是:C#里是可以对一个对象赋函数值的。解决这个问题的办法是先对该对象申明,申明它可以被什么样的函数来赋值,而这个对象申明在C#里的学名就是委托。

(在C++里称为函数指针申明,相应的对象也就叫做函数指针。Java里也不同的叫法,可惜我不知道。)

而它的语法就是:

delegate [function declare];

这里的function declare就包括了:

1、             函数返回类型,

2、             可用来存放函数的对象名(也就是委托名)

3、             函数参数

所以完整的定义可以是:

delegate int MyDelegate(object I_object);

当然,合法的委托定义可以是:

delegate void MyDelegate();

delegate void MyDelegate(object I_1,object I_2);

现在,上面的语法就定义了一个抽象的对象MyDelegate, 注意,这里说的是抽象的对象,也就是说,你不能直接给MyDelegate赋函数,而只能在它的实例上函数,这是C#里特殊的要求。它的语法是:

MyDelegate m_delegate = new MyDelegate(MyDelegate申明一致的函数名);

例如,以下是一个完全的,合法的委托申明与实例化一个对象:

delegate int MyDelegate(object i_object);
//
MyDelegate m_delegate = new MyDelegate(MyWrite);
//MyWrite函数如下,它是满足委托的申明的。
                   static private int MyWrite(object i_string)
                   
{
                            Console.Write(i_string);
                            
return i_string.ToString().Length;
                   }

现在我们就很好的解决了第一个问题,如何定义一个对象,使该对象可以把函数当变量来赋给它。而且,可以区别不同的函数类型,主要是通过函数返回值与函数参数来共区别一类函数。

OK,第二个问题:如果有了这样的一个对象后,如何来给它赋一个函数值呢?

其实上面的实例化一个委托对象时,就已经给它赋值了。上面的代码中,m_delegate就已经被赋值MyWrite,因此它已经具有了MyWrite函数的功能。

还有其实它的方法来给它赋值吗?有,在委托的一个应用中,可以看到其它的赋值方法。也就是另一个不好理解的概念:事件!后面会提到。

我们再来看一下最后一个问题:如何通过一个已经赋值好了的委托对象,还调用它上面赋值了的函数。

这个最简单了,当一个委托实例赋了函数对象在上面后,就可以像调用原函数一样的来调用它了。因此,下面是一个会法的调用:基于上面的申明。

m_delegate(“This is a delegate object to call the raw function.”);

它就等同于:

MyWrite(“This is a delegate object to call the raw function.”);

因此,上面的调用与原函数调用一样,会返回一个int结果。

OK,最后看一个完整的例子:

using System;
namespace ConsoleApplication1
{
    
class Class1
    
{
        
//先申明一个委托对象。
        delegate int MyDelegate(object i_object);
        [STAThread]
        
static void Main(string[] args)
        
{
            MyDelegate m_delegate 
= new MyDelegate(MyWrite);
            m_delegate(
"This is a delegate object to call the raw function.");
        }

        
//该函数是满足上面委托对象的申明的。
        static private int MyWrite(object i_string)
        
{
            Console.Write(i_string);
            
return i_string.ToString().Length;
        }
        
    }

}


再来讨论一下它的应用:事件!

事件是其于委托的。我们还是先来看最开始的那个例子:

object m_obj = m_isRight?MyWrite("true"):MyWrite("false");

我想把一个函数对象赋值到m_obj上!但上面的委托只能在实例化对象的时候就直接给它赋值了。而现在是,在运行时对一个委托赋函数值。可以做到吗?

同样是有这样的向个问题,当然,前提是我们已经知道有一种对象叫委托,它的实例可以赋函数对象。

下面的问题是:

1、             如果可以在运行时给某个“特殊委托”赋函数对象,如何实现?

2、             运行时,如何知道该“特殊委托”是否已经被赋过函数值?及如何再赋值?

3、             如果可以,能否在一个“特殊委托”上添加多个函数?如果可以,如何删除函数?

下面,我们就针对这几个问题,来讨论一下C#里的事件,也就是上面的“特殊委托”。(其它语言里是如何实现这些功能的,我就不清楚了。)

首先,C#里是可以实现在运行时给一个委托动态的赋函数值的,同时也是可以动态的删除已经添加在某个委托上的函数的,它的实现有一点点麻烦,就是要用到另一个对象:事件!event

(申明,你完全可以不把它叫事件,只不过这种动态的添加和删除函数的功能在真实的程序设计中,基本上是与事件相关,所以就叫做事件了。个人想法,呵呵。)

OK,下面是C#语法,来申明一个“特殊委托”――事件,让它可以动态的添加函数!

下文中,事件是指那些“特殊委托”,它的特殊之外,后面会讲到。而下文中的委托就是前面讲到的,一个特殊的对象,该对象可以把函数当“值”赋给它。

static event MyDelegate m_myevent;

(static 可以用其它的修饰符)

说明一下,这里其实就是申明了一个事件,用event来说明它是事件(特殊委托)的。其实对比实例化一个委托的语法,你可以理解到,它就像是申明了一个委托,只不过个委托加了个event来说明它:

Mydelegate m_delegate;//申明一个委托

event MyDelegate m_myevent;//申明一个事件

很像吧!不是吗?

OK,我们再来看,m_myevent m_delegate到底有什么不同的?也就是,事件(特殊委托)到底特殊在什么地方?

1、             事件不是一个可以直接把函数当值一样赋给它的委托。而委托可以直接赋函数,而且是在实例化的时候,赋函数名。

2、             事件只能把一个实例的委托当值赋给它。也就是说:事件是用来管理委托的,进而来管理函数!因为一个实例化的委托一定有一个函数与之对应。

3、             在事件上可以动态的添加与删除委托。而委托上不能动态的添加删除函数。

OK,下面的一个问题,上面事件的申明中,MyDelegate是起什么作用的呢?

还记得前面的委托申明吗?它就是说明了m_myevent在运行时可以动态的以委托的形式赋的函数要与MyDelegate申明的一样!

因此上面的一个实例化是完全合法的。

再理解一下:事件,是用来动态管理委托的,而委托是单一的与一个函数对应的。

 

现在看第二个问题,运行时,如何知道该“特殊委托”是否已经被赋过函数值?及如何再赋值?

即:如何知道一个事件上已经赋过经过委托过的函数?

前面已经说过,m_myevent没有给它赋值,如何给它赋值呢?它的赋值方法有点怪:

一个实例:

m_myevent += m_delegate;

有点怪吧!这里正好说明了:事件是用来动态管理委托的。把一个委托加在事件上。

当然,你还可以在添加委托的时候直接new一个新的委托:

m_myevent +=new MyDelegate(Class1_m_myevent);//后面的函数名由vs2003生动生成

这就是.net下标准的给事件添加委托的方法,也就是给一个事件添加了一个可调用的函数。确切的说,是一个回调函数(C++的概念)。

 

OK,下面就如何判断一个事件上是否已经被赋过经过委托的函数:

if(m_myevent==null)

就可以知道了!

那么如何知道一个事件上面有多少个委托呢?也就是多少个委托过的函数?

m_ myevent.GetInvocationList();

可以得到所有的委托!这里应该知道:事件本身是一个对象,当实例化一个事件后,它是有自己的一些方法也成员的。可以查阅MSDN得到更多说明,同样的委托也是一个对象,相关的说明也可以在MSDN里找到。

最后的问题:如何删除一个已经添加在事件上的委托?

太容易而且太有意思了:

m_myevent -= m_delegate;

那么这样的几个问题又来了:

1.             如果这个事件上没有该委托,“减掉”以后会出错吗?不会,放心的减吧。

2.             如何调用这个事件上的委托呢?上面有多个委托,它是怎样运行呢?

调用事件上的委托与调用委托上的函数是完全一样的:你要给出与委托申明一样的函数参数,并且调用会返回与申明一样的数据类型。

最后再回来看这个问题:

object m_obj = m_isRight?MyWrite("true"):MyWrite("false");

如何解决它呢?把一个函数赋给一个对象:一个例示(仅做演示解决这个问题,个人认为这样的做法没有实用意义)

using System;
namespace ConsoleApplication1
{
    
public class Class4
    
{
        
event System.EventHandler m_obj;
        
public Class4()
        
{
            System.EventHandler m_f1 
= new EventHandler(SomeFunc1);
            System.EventHandler m_f2 
= new EventHandler(SomeFunc2);
            
bool m_someCondition = false;
            m_obj 
+= m_someCondition?m_f1:m_f1;
            m_obj(
this,null);
        }


        
private void SomeFunc1(object sender, EventArgs args)
        
{
        }


        
private void SomeFunc2(object sender, EventArgs args)
        
{
        }

    }

}


最后看一个完整的例子:

using System;
namespace ConsoleApplication1
{
    
class Class1
    
{
        
//先申明一个委托对象。
        delegate int MyDelegate(object i_object);
        
static event MyDelegate m_myevent;
        [STAThread]
        
static void Main(string[] args)
        
{
            MyDelegate m_delegate 
= new MyDelegate(MyWrite);
            m_delegate(
"This is a delegate object to call the raw function.");
            m_myevent 
+= m_delegate;
            m_myevent 
+= new MyDelegate(MyWrite);
            m_myevent 
+=new MyDelegate(Class1_m_myevent);
            
if(m_myevent!=null)
            
{
                m_myevent(
"This is a event to call the funcaion on the delegate.");
            }
            
        }

        
//该函数是满足上面委托对象的申明的。
        static private int MyWrite(object i_string)
        
{
            Console.WriteLine(i_string);
            
return i_string.ToString().Length;
        }


        
private static int Class1_m_myevent(object i_object)
        
{
            Console.WriteLine(i_object);
            
return 0;
        }

    }

}

 

我们再来看一个.net下标准的事件驱动模型的例子:

using System;

namespace ConsoleApplication1
{
    
public delegate void MyEventHandle(object i_sender,object i_arg);

    
public class Class2
    
{
        
public Class2()
        
{
        }


        [STAThread]
        
static void Main2(string[] args)
        
{
            Class3 m_runOject 
= new Class3();
            m_runOject.OnError 
+= new MyEventHandle(m_runOject_OnError);
            m_runOject.OnSomeThingHappened 
+= new MyEventHandle(m_runOject_OnSomeThingHappened);
            m_runOject.Run();
        }


        
private static void m_runOject_OnError(object i_sender, object i_arg)
        
{
            Console.WriteLine(
"Error in {0}, arg:{1}",i_sender,i_arg);
            Console.WriteLine(
"Object {0} will stop running.",i_sender);
            (i_sender 
as Class3).Stop();
        }


        
private static void m_runOject_OnSomeThingHappened(object i_sender, object i_arg)
        
{
            Console.WriteLine(
"Something happended in {0}, arg:{1}",i_sender,i_arg);
        }

    }



    
public class Class3
    
{
        
public bool m_isStop = false;
        
public event MyEventHandle OnSomeThingHappened;
        
public event MyEventHandle OnError;

        
public Class3()
        
{
        }


        
public void Run()
        
{
            Random m_rand 
= new Random();
            
int m_randomNum = m_rand.Next();
            
while(!m_isStop)
            
{
                
if(m_isStop){break;}
                m_randomNum 
= m_rand.Next(100);
                
if(m_randomNum%5==0)
                
{
                    
if(this.OnError!=null)
                    
{
                        
this.OnError(this,m_randomNum);
                    }

                }

                
else
                
{
                    
if(this.OnSomeThingHappened!=null)
                    
{
                        
this.OnSomeThingHappened(this,m_randomNum);
                    }

                }

            }

        }


        
public void Stop()
        
{
            m_isStop 
= true;
        }

    }

}


好了,全部完了!

 

最后再从另一个角度来理解:函数,委托和事件!

1、             函数,是程序的基本单元,在.net下,有一个很重要的思想,就是:一切对象化!你可把一个int boxing后得到一个object,也可以把一个object unboxing后得到一个int. 可惜,就是没有函数对象化这个概念!?

2、             其实函数也可以对象化!就是通过delegate,委托就是把函数作为对象来处理,使它函数也具有了对象的一些特点。在实例化一个委托的时候,就是把一个函数”boxing”。因此,委托本质上就是一个类!它的初始化参数是一个函数名!不同的委托是不同的类,对应不同类型的函数。

3、             事件是对委托的一个管理封装,它可以很好的动态管理委托,从而完成很多有实用价值的事情,最主要的就是事件!

完全是个人理解,有不同意见,欢迎讨论!

================================
  /\_/\                        
 (=^o^=)  Wu.Country@侠缘      
 (~)@(~)  一辈子,用心做一件事!
--------------------------------
  Happy Jimmy, keep dreaming!  
================================

posted on 2006-11-29 10:09 Wu.Country@侠缘 阅读(3916) 评论(30)  编辑 收藏 网摘

评论

#1楼 2006-11-29 10:44 柠檬      

好文章慢慢理解   回复  引用  查看    

#2楼 2006-11-29 10:52 aska[匿名][未注册用户]

不错   回复  引用    

#3楼 2006-11-29 11:30 Anders Cui      

楼主写的很详细啊

我觉得可以这么理解:
委托是对函数的抽象
它的一个实例对应一个具体的函数
看起来像函数的指针

事件是一个容器(委托的实例的容器)
它可以包含0个或多个委托的实例
  回复  引用  查看    

#4楼 2006-11-29 12:07 Jeffrey Zhao      

@Anders Cui
我们使用的Delegate对象都是MulticateDelegate,能够保留很多个函数,这一点和event一样。
  回复  引用  查看    

#5楼 2006-11-29 13:58 Anders Cui      

@Jeffrey Zhao
谢谢了,以前没注意到这一点:)
  回复  引用  查看    

#6楼 2006-11-29 14:51 喜欢吹风的感觉      

刚试了一下,有个疑问,觉得一个事件装载了多委托后,在使用时,却不能给任何提示.这样的话在使用时,我不能直观的知道,到底这个事件到到底有哪些函数可以用.它们大概的特征是什么.(如果像一个对象一样能有智能感知,知道有哪些方法,和这些方法的特征,那就比较方便了.)   回复  引用  查看    

#7楼 2006-11-29 15:41 Jeffrey Zhao      

@喜欢吹风的感觉
一个事件都是通过一个Delegate定义的,能够用的函数就是和这个Delegate的签名相同的函数。
  回复  引用  查看    

#8楼 2006-11-29 16:20 ocean2000[匿名][未注册用户]

通俗易懂,相当喜欢。   回复  引用    

#9楼[楼主] 2006-11-29 16:24 Wu.Country@侠缘      

@喜欢吹风的感觉
你的担心是多余的!
1、能够添加到一个事件上的函数一定满足委托所申明的。
2、所有添加到事件上的函数都是可以正确调用的!除非你的函数内部自己有问题。
3、如果你想知道一个事件上有多少个委托函数,可以用事件类的一些方法来取得,如前面说到的:m_ myevent.GetInvocationList();

@Jeffrey Zhao
你说的MulticateDelegate让我想到了Jeffrey书中提到的一个问题,具体的情况我记的不是很清楚了,大概的是说.net的设计框架里存在这样的一个BUG,就是单委托和多委托本质上是一样的。具体的我今天回去查一下,晚上再做评论。
(注:是.net1.1框架下)
  回复  引用  查看    

#10楼 2006-11-29 17:00 tmfc      

说的很不错,很清楚。
所说的MulticateDelegate的问题应该是指.net 1.1的委托(由编译器自动生成)其实都是从MulticateDelegate继承,并没有从Delegate继承吧,这是历史原因导致的,不知道2.0里面有没有修掉。
  回复  引用  查看    

#11楼 2006-11-29 17:27 数据绑定者      

先留个记号,慢慢看   回复  引用  查看    

#12楼 2006-11-29 17:38 王林波      

good!!真的说得相当的易懂!这种文章应该得奖才对!建议写入教科书!或者msdn!   回复  引用  查看    

#13楼 2006-11-29 17:56 Klesh Wong      

1、Delegate是多播的,和event一样可以自加自减。
2、event我想,还有一个比较特别的地方就是不需要实例化它,或者说编译器帮你实例化了它。
  回复  引用  查看    

#14楼[楼主] 2006-11-29 18:12 Wu.Country@侠缘      

@王林波
谢谢你的评论了,有点夸张了。呵呵。

@tmfc
你说的正是这个问题!
Jeffrey Richter的书中说明MulticateDelegate与Delegate的区别:
<Applied Microsoft .net Framework Programming>P375
这是MS的一个小失误,其实在1.1下,所有的委托都是从MulticateDelegate继承的!这是个历史问题,就不多说了。
  回复  引用  查看    

#15楼 2006-11-29 19:53 Justin      

事件也可以这么理解:某个事件是基于某个委托类型的函数集合。因为事件管理的也都是相同类型的委托吧。   回复  引用  查看    

#16楼 2006-11-29 23:58 锦瑟[未注册用户]

恩,还行,就是有点浅了,其他以后有更深入的内容。   回复  引用    

#17楼 2006-11-30 11:00 Bryant      

不错,说的很好
对委托和事件有了个更好的理解,谢谢,但我还有个疑问,什么时候要用委托了,不用就不行了,呵呵
望大家不要笑我
  回复  引用  查看    

#18楼[楼主] 2006-11-30 14:17 Wu.Country@侠缘      

@锦瑟
呵呵,那要看目标读者群是什么!我这里就是写给那些对委托和事件理解不是很清楚的读者写的。要是谈到委托与事件的应用,还是有很多东西可以深入研究一下的。有空我也再写一篇委托事事件的应用。

@Bryant
委托与事件的应用还是很灵活的,至于什么时候一定要做,什么时候不用,这还不好说,很多其它编程语言里没有委托与事件的概念,一样可以完成很多工作。
至于一般情况下的应用,文章中最后一个例子就是很好的说明。
我再补充解释一下:
类Class3中有一个Run函数执行自己的工作!在正常情况下,Run函数就保持自己的工作正常运行就行了。但有时可能会出现一些异常,例如,例子中的m_randomNum%5==0就是一个特殊情况发生。这时,Class3已经不能正常运行了(当然,这里是假设)。这里就要做一些善后的处理工作!
当然,Class3自己做自己的善后处理是很容易的!但问题是,Class3并不知道它自己出现异常后,对别人会有什么影响(例子中的Class2)!所以,这时就可以加一个OnError事件,凡是因Class3出现异常要进行一些处理工作的对象,都可以响应这个OnError事件,然后执行他们要处理的事件!最后,Class3再自己做一些处理。这就是一个最简单的应用。当然,例子中的Class2只是输出一些文字,然后停止了Class3!如果Class2是依奈于Class3的,当Class3出现异常,如果要再次启动Class3,这时用事件是最好的了。当然,这里只是一个例子。
它的应用在.net下还是很灵活的,特别是在Windows平台下。
  回复  引用  查看    

#19楼 2006-11-30 18:34 鲁鲁      

不错,支持一下。   回复  引用  查看    

#20楼 2006-12-01 13:35 Boler Guo      

不错,这是我看过的讲事件、委托讲的最通俗易懂一片文章。如果可能可以考虑顺着这篇文章再深入的多讲一些东西,那就更完满了!   回复  引用  查看    

#21楼 2006-12-05 12:45 yf[未注册用户]

“委托是将引用存储为函数的类型”记得第一次看到书上看到的的定义是这样的...其实并不十分理解。学习一下(收藏)。。。。。。。。

终于看到更新了,难得....呵...
  回复  引用    

#22楼[楼主] 2006-12-05 16:57 Wu.Country@侠缘      

感谢你还一直关注我的博客!我会继续努力写些好文章的。   回复  引用  查看    

#23楼 2006-12-10 15:51 eDgAr suN      

好文章啊 多么希望出于自己的手啊 呵呵....   回复  引用  查看    

#24楼 2007-08-05 10:16 壁虎[未注册用户]

很好   回复  引用    

#25楼 2007-09-11 14:53 帝之晓1[未注册用户]

非常不错,一直搞不懂委托和事件,看了这个就豁然开朗了   回复  引用    

#26楼 2007-12-27 12:57 张盼[未注册用户]

谢了,讲解的还不错,让我理解了不少.
呵呵,我是初学者,请问一下,我怎么总觉得事件和方法没什么区别
反复的调用一个方法和调用事件有什么区别啊
望指点
请问:您能不能讲解一下C#接口的知识啊??
  回复  引用    

#27楼 2008-03-04 16:09 饮酒买醉      

谢谢,偶一直对委托和事件犯糊涂。看了这篇入门的文章清晰了很多。特别是“事件,是用来动态管理委托的,而委托是与函数对应的”这句话,能使入门者很容易的理解。   回复  引用  查看    

#28楼 2008-05-27 12:29 微笑装坚强[未注册用户]

非常感谢LZ,今天一上午都在看这个委托和事件
看了很多文章,也没有闹的很明白,到底是做什么的(特别系统生成的那些事件和自定义事件的对比)
看了LZ的文章,和最后的那个例子 感觉明朗的很多

谢谢。
  回复  引用    

#29楼 2008-05-27 12:31 微笑装坚强[未注册用户]

楼主说的那篇 更 深入 的文章,我怎么没找到呢

能给我链接吗?

谢谢。
  回复  引用    




发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 576030




相关文章:

相关链接: