代码改变世界

C#基础知识----委托使用总结

2012-05-01 10:28  ClarkZhou  阅读(241)  评论(0编辑  收藏  举报

说到委托,大家都应该能有一个清晰的认识了。如果,您还不知道什么是委托或者还存在一些疑问,您不妨可以参考:JimmyZhang前辈的文章。本篇将从使用的各个方面来进行总结。

一、从方法说起

一个简单的认知:如果要使用委托,那么它和方法是分不开的,从方法本身来讲,可分为两类:

1、有返回值 

带参数

 static int Main(string[] args)

{
    return 0;
}
不带参数

 static int Main()

{
    return 0;
}

 

2、无返回值 

带参数

 static void Main(string[] args)

{
 
}
不带参数

 static void Main()

{

 

二、委托-Delegate

      经过C#编译器之后,一个Delegate 的声明将被编译成一个类,类里面包含了几个比较主要或者需要我们关心和注意的方法:异步调用方法(BeginInvoke 、EndInvoke)、同步调用方法(Invoke),这些信息都可以通过IL代码查看工具获取到(如:ILDasm.exe、Reflector、ILSpy等),此处不再详述。

      委托又被称为方法代理,通过前面的认识可知,它其实就是方法的代理类,即:使用一个类型来封装方法的行为,从而就可以将方法当作一个类型对象(注:特殊的类型,下文中起名为:方法类型,仅使用于本文)来使用。更进一步说,一个具体的代理类是由需要被代理的方法的签名(返回值+参数列表)来确定的。反过来讲,如果方法不需要被当作方法类型对象来使用或对待,那么委托就没有存在的必要。当然,这里也是根据个人的理解所以得出的结论,如果有问题请指出。

下面就来看看怎么用或者说它有哪些用处:

1、【方法类型参数】:既然已经知道它是一个类型,我们应该能很自然的想到,把它当作参数来使用,下面是一段很简单的代码:

 View Code

 将方法当作参数进行传递
using System;

namespace Study
{
    /// <summary>
    
/// 2、声明一个代理类
    
/// 名字可以随便取,但,签名和返回值要跟被代理的方法一样
    
/// </summary>   
    public delegate void MyMethodDelegate(string name, int age);
    internal class DelegateStudy
    {
        /// <summary>
        
/// 1、首选需要确定有这么一个需要被代理的方法-没有返回值        
        
/// </summary>       
        public void MyMethod(string name, int age)
        {
            Console.WriteLine("Invoke MyMethod,Name:{0} , Age:{1}", name, age);
        }
    }
    /// <summary>
    
/// 3、为了更直观,再定义一个(可以/需要)使用代理的类型
    
/// </summary>
    internal class UseMethodDelegate
    {
        /// <summary>
        
/// 根据.Net设计规范,说是变量的声明不要带任何前缀,为了和参数区别这里还是带了一个下划线前缀
        
/// </summary>
        MyMethodDelegate _methodDelegate;
        public UseMethodDelegate(MyMethodDelegate methodDelagate)
        {
            this._methodDelegate = methodDelagate;
        }
        /// <summary>
        
/// 调用方法
        
/// 注意:方法的参数不是必须要声明的,此处是为了更进一步的演示    
        
/// </summary>       
        public void InvokeMehod(string name, int age)
        {
            Console.WriteLine("UseMethodDelegate.InvokeMehod Run...");
            if (this._methodDelegate != null)//必须判断
            {
                this._methodDelegate(name, age);//如果不声明参数则需要传递相应的具体参数
            }
        }
        //至此一个代理方法的架子就定义完了,下面看具体使用
    }

    class UseApp
    {
        static void Main()
        {
            //拿到被代理方法所在对象
            DelegateStudy delegateStudy = new DelegateStudy();
            //直接将方法:delegateStudy.MyMethod传给了UseMethodDelegate类型的构造函数
            UseMethodDelegate userMehodDelegate = new UseMethodDelegate(delegateStudy.MyMethod);
            //在InvokeMehod方法中调用了被代理的方法
            userMehodDelegate.InvokeMehod("张三"50);
        }
    }

对于上面的代码,几个问题:

1)、怎么确定哪个或哪些方法需要被代理?这个需要根据具体的情况来分析,根据您对面向对象的理解以及从各方面获得的信息来确定,说白了就是需要经验。

2)、对于被代理方法来说,它跟具体的被代理的方法名无关,它将根据委托声明时的参数签名(返回值+参数列表)来确定。也就是说只要方法A和方法B的签名一样(当然功能可以不一样),那它们就可以被同一个委托所代理。

3)、既然跟名字无关,那可不可以有不带名字的方法呢?写过JS代码的人都知道,答案是肯定的,C#也没让您失望,下面将介绍匿名方法

 

2、【匿名方法】:C#2.0提供的功能,说白了就是没有具体的方法名,但可以有返回值和参数列表的方法,在C#中匿名类型是不能简单的直接声明,它必须要是一个具体的代理类类型,即使在可以使用var 的C#3.0中也不行,使用匿名方法改造上面的代码:

 

View Code 
using System;

namespace Study
{

    public delegate void MyMethodDelegate(string name, int age);

    #region 已经不需要了
    /*
    internal class DelegateStudy
    {
        /// <summary>
        /// 1、首选需要确定有这么一个需要被代理的方法-没有返回值        
        /// </summary>       
        public void MyMethod(string name, int age)
        { 
            Console.WriteLine("Invoke MyMethod,Name:{0} , Age:{1}", name, age);
        }
    }
     
*/
    #endregion

    internal class UseMethodDelegate
    {

        MyMethodDelegate _methodDelegate;
        public UseMethodDelegate(MyMethodDelegate methodDelagate)
        {
            this._methodDelegate = methodDelagate;
        }

        public void InvokeMehod(string name, int age)
        {
            Console.WriteLine("UseMethodDelegate.InvokeMehod Run...");
            if (this._methodDelegate != null)
            {
                this._methodDelegate(name, age);
            }
        }

    }

    class UseApp
    {
        static void Main()
        {
            #region 已经不需要了

            //拿到被代理方法所在对象
            
//DelegateStudy delegateStudy = new DelegateStudy();
            
//直接将方法:delegateStudy.MyMethod传给了UseMethodDelegate类型的构造函数
            
//UseMethodDelegate userMehodDelegate = new UseMethodDelegate(delegateStudy.MyMethod);
            #endregion

            UseMethodDelegate userMehodDelegate = new UseMethodDelegate(delegate(string name, int age)
                {
                    Console.WriteLine("Invoke AnonymousMethod,Name:{0} , Age:{1}", name, age);
                });

            //在InvokeMehod方法中调用了匿名方法
            userMehodDelegate.InvokeMehod("张三"50);

            Console.ReadKey();
        }
    }

除方法标识串以外,其它输出一样,非常简单,但是比较强大,当然不光体现为节省了代码,对于匿名方法的使用还需要注意:

1)、一定要理解为什么需要匿名方法,或者说自己愿意不愿意使用,它本质上和传递具体方法是一样的:它还是一个方法。匿名方法则提供了更方便的功能。

2)、如果一定要说传递匿名方法和传递具体方法有什么区别,那应该就在于方法实现者的区别,比如,您必须传递由别人提供的方法来完成某项工作,并且方法的具体实现是保密的,在正常情况下您可能只能通过【对象名.方法】的方式进行传递(当然您也可以在匿名方法中完成必须方法的调用);匿名方法则不然,使用者对匿名方法应该有完全控制权。

3)、到现在为止,好像还只看到了匿名方法为我们做了一件很普通的事情:减少了代码量。接下来看看匿名方法还有那些用处:

 Lambda表达式,为了简单,继续改造上面的代码:

 对于匿名方法和Lambda的区别只是进一步简化了匿名方法的使用,下面只对需要传递匿名方法处进行改写: 

//也不需要了
 /* 
UseMethodDelegate userMehodDelegate = new UseMethodDelegate(delegate(string name, int age)
                {
                    Console.WriteLine("Invoke AnonymousMethod,Name:{0} , Age:{1}", name, age);
                });
            
*/ //如果只有一个参数的方法,可以省略参数的小括号和参数声明的类型,如果只有一行代码(一个分号),连大括号也可以省略如果有返回值,则连return也可以省 
UseMethodDelegate userMehodDelegate = new UseMethodDelegate((string name, int age) =>
                {
                    Console.WriteLine("Invoke Lambda,Name:{0} , Age:{1}", name, age);
                }
            );

 Lambda表达式的用处就不多说了:Linq扩展方法、老赵的 使用Lambda表达式编写递归函数等。使用匿名方法实现闭包功能等,如果有兴趣的同学可以了解一下这些方面的资料,不一定要用,但可以作为兴趣学一下大牛们的思路。

3、【异步】

什么是异步的理论我也就不多说了,前面的提到过两个方法:BeginXXX和EndXXX,可以用来实现委托的异步功能,具体示例可以参考相关的资料,如:【MSDN】、李鱼大侠的 C#客户端的异步操作,如果对这方面感兴趣的同学可以去学习和研究下,我就不班门弄斧了.

4、【异步编程 】

当然,异步编程的学问我也只是略懂皮毛,如果您认为异步编程跟C#中的委托没有什么必然的联系,那么,基于委托的异步编程模式也许可以为您学习这种模式提供一些思路,想了解更多这方面的知识,可以多向大师们学习,如: 赵大师 等。 

 

另外,.Net的委托属于多播委托,它像存储结构的链表一样,可以形成一个委托链,并且可以触发链上的每个方法,同时委托也具有协变和逆变的特性。

如果,您还有其它方面的应用或者我没提到的地方,希望您能提出来, 供大家参考和学习。

三、事件-Event

      说到委托,如果不提事件就好像少了点什么,就好像吃鸡蛋忘记剥壳的感觉(可能不恰当,理解就好)。甚至,有些人认为委托和事件是密不可分的,我觉得这是事件重要性的一种体现,我认为并没有什么错。既然这么重要我就把它给单独列出来了,做过.net开发的人应该都知道基于“事件编程”模型,在这之前如果您需要做一个简单的基于WINODWS的应用程序,估计(因为在这之前本人还真没做过,所以只能估计,请见谅)您就需要做很多其它的工作,从而加大了开发成本,同时提高了开发门槛。有了事件模型,您可以在自动生成的按钮单击事件的方法中用您熟悉的.net平台语言来很方便、快捷的完成单击需要完成的工作,您只需要专注于您的业务逻辑,甚至您都不需要关心事件到底是什么。

      可以把事件看作是一种基于发布-订阅的消息模式,学过设计模式的人肯定很明白,其实事件模式,就是23个设计模式中的【观察者模式(Observer)】,具体可以参考设计模式相关的资料。只不过.net中的事件是观察者模式的改进版本,可以说,事件是一个更加面向对象或更方便满足开发需要的强大的编程模式,下面来看一下怎么定义和使用事件:

 

View Code 
using System;
using System.Threading;

namespace Study
{
    ///说明-模拟交通系统,汽车需要根据红绿来做出相应的反应
    
/// <summary>
    
/// 红绿灯颜色
    
/// </summary>
    public enum TrafficLightsType
    {
        Red, Green, Yellow
    }
    /// <summary>
    
/// 基于.net事件的标准写法---2.0之前
    
/// </summary>
    
/// <param name="sender"></param>
    
/// <param name="e"></param>
    public delegate void TrafficLightsEventHandler(object sender, TrafficLightsEventArgs e);
    /// <summary>
    
/// 业务对象:发布者-红绿灯
    
/// 他有一个事件:变换亮灯的颜色
    
/// </summary>
    public class TrafficLights
    {
        /// <summary>
        
/// 发布一个事件
        
/// </summary>
        public event TrafficLightsEventHandler ChangeLight;
        /// <summary>
        
/// 触发事件
        
/// </summary>
        
/// <param name="lightType"></param>
        public void OnChangeLight(TrafficLightsType lightType)
        {
            TrafficLightsEventHandler changeLight = ChangeLight;
            if (changeLight != null)//必须
            {
                changeLight(thisnew TrafficLightsEventArgs(lightType));
            }
        }
    }
    public class TrafficLightsEventArgs : EventArgs
    {
        public TrafficLightsEventArgs(TrafficLightsType lightType)
        {
            LightType = lightType;
        }
        public TrafficLightsType LightType { getprivate set; }
    }
    /// <summary>
    
/// 业务对象:订阅者-汽车
    
/// 这里是一个强依赖,如果有必要可以采用相应重构措施,这里就不多写了
    
/// </summary>
    public class Car
    {
        //它要知道有这么一个东西,并对他产生了兴趣
        
//
        
//相当于给个牌照
        public string Name { getprivate set; }
        TrafficLights _trafficLights;
        public Car(TrafficLights trafficLights, string name)
        {
            Name = name;
            _trafficLights = trafficLights;
        }
        /// <summary>
        
/// 订阅事件
        
/// </summary>
        public void Add()
        {
            if (_trafficLights != null)
            {
                _trafficLights.ChangeLight += new TrafficLightsEventHandler(TrafficLights_ChangeLight);
            }
        }
        /// <summary>
        
/// 解除订阅-为了演示
        
/// </summary>
        public void Remove()
        {
            if (_trafficLights != null)
            {
                _trafficLights.ChangeLight -= new TrafficLightsEventHandler(TrafficLights_ChangeLight);
                //这个做了一个与事件本身没有多大关系的处理,它应该是一个业务规则:
                
//不遵守红绿灯规则的车,就一直跑,而不是直接不管了。
                
//这里主要是为了方便演示,真正的业务规则的处理不应该在这里,可以自己视情况而定
                this.Run();
            }
        }
        /// <summary>
        
/// 事件的
        
/// </summary>       
        public void TrafficLights_ChangeLight(object sender, TrafficLightsEventArgs e)
        {
            if (e.LightType == TrafficLightsType.Green)
            {
                this.Run();
            }
            else this.Stop();
        }
        void Run()
        {
            Console.WriteLine("{0} Run......"this.Name);
        }
        void Stop()
        {
            Console.WriteLine("{0} Stop......"this.Name);
        }

    }
    //调用
    public class EventCustom
    {
        static void Main()
        {
            #region 调用方式一
            //建立一个红绿灯对象
            TrafficLights tl = new TrafficLights();
            //让汽车对象订阅红绿灯的改变事件
            Car car1 = new Car(tl, "Car1"); car1.Add();
            Car car2 = new Car(tl, "Car2"); car2.Add();
            Car car3 = new Car(tl, "Car3"); car3.Add();
            Car car4 = new Car(tl, "Car4"); car4.Add();
            #endregion
            //调用方式二
            
//思路转换后的调用方法,不需要让Car对和TrafficLights产生依赖,当然道理还是一样的
            Car car5 = new Car(null"Car5");
            //有了这句就不需要Add方法了,但又带来了一个问题 trafficLights_ChangeLight 被公开了
            
//有兴趣的同学可以去进一步学习一下,我就不多说了
            tl.ChangeLight += car5.TrafficLights_ChangeLight;

            tl.OnChangeLight(TrafficLightsType.Green);
            Thread.Sleep(3000);
            Console.WriteLine("3秒后...");
            car4.Remove();//比如他闯红灯了
            tl.OnChangeLight(TrafficLightsType.Red);

            Console.ReadKey();
        }
    }

 

      很简单,也很好理解,这就是事件。是的,它基于委托,使用+=和-=来注册和移除事件的订阅,当然一个真正的业务系统不可能就这么轻松的完成了,还需要考虑很多其它方面的因素,这里就不作过做的说明了,如果以后有机会,倒是可以探索一下。

      以上,就是本人对C#中委托使用的总结,如果您觉得有什么问题或者错误的地方,希望您能帮忙指出来,本人先表示感谢,同时我将即时更新和处理,以免误人子弟了,我可伤不起。下面再来看看.Net对委托的进一步改进:

 

四、 .Net的三种委托封装:

泛型为.NET开发平台增添了许多光彩。下面将介绍.NET中基于泛型的委托改进:

1、Func<>:一个泛型类型对象,可以表示有返回值的方法的委托类型,如果只有一个参数就表示不带参数且有返回值的方法代理类,它的具体作用就是,以介绍【匿名方法】lambda表达式一些的示例为基础进行改进代码如下: 

View Code 
using System;

namespace Study
{

    //这个都不需要了
    
//public delegate int AddDelegate(int x, int y);

    internal class UseMethodDelegate
    {
        /// <summary>
        
/// 为了方便演示,直接定义了静态方法,注意:这里的委托参数当然可以跟前面一样在构造函数里面初始化
        
/// </summary>
        public static int Add(int x, int y, Func<intintint> delegateAdd)
        {
            if (delegateAdd != null)
            {
                return delegateAdd(x, y);
            }
            return 0;
        }

    }

    class UseApp
    {
        static void Main()
        {
            int result = UseMethodDelegate.Add(1020, (int x, int y) => x + y);
            Console.WriteLine(result.ToString());

            Console.ReadKey();
        }
    }

Func<>提供了几个类型参数重载,最后一个参数表示返回值类型,如果只有一个参数则表示:不带参数的有返回值方法的代理类型对象。

如果重载的所有类型不能满足您的要求,你就需要手动通过delegate来声明委托了。  

 

2、Action:非泛型版本,表示既无返回类型也不带参数的方法代理对象;Action泛型版本,表示带参数的无返回值方法的代理类型对象,具体是使用方式同Func<>一样,就不再写代码了。

注:就是因为基于这两个对象,我才在开始做了一个简单的方法分类,看到这里印象估计会深一些。 

 

3、EventHandler:主要用于事件,非泛型版本,表示不需要传递具体EventArgs类型对象的事件声明,比如Button 的click事件;EventHandler泛型版本,表示需要传递具体EventArgs类型对象的事件声明,如上面的模拟红绿灯交通系统的示例中的TrafficLightsEventArgs类型对象。

 

如果您觉得还有什么疏漏的地方,还请帮忙指出来 

<--End-->

 参考资料:

[MSDN]

老赵点滴 - 追求编程之美

Fish Li 

张子阳