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
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中也不行,使用匿名方法改造上面的代码:
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中的事件是观察者模式的改进版本,可以说,事件是一个更加面向对象或更方便满足开发需要的强大的编程模式,下面来看一下怎么定义和使用事件:
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(this, new TrafficLightsEventArgs(lightType));
}
}
}
public class TrafficLightsEventArgs : EventArgs
{
public TrafficLightsEventArgs(TrafficLightsType lightType)
{
LightType = lightType;
}
public TrafficLightsType LightType { get; private set; }
}
/// <summary>
/// 业务对象:订阅者-汽车
/// 这里是一个强依赖,如果有必要可以采用相应重构措施,这里就不多写了
/// </summary>
public class Car
{
//它要知道有这么一个东西,并对他产生了兴趣
//
//相当于给个牌照
public string Name { get; private 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表达式一些的示例为基础进行改进代码如下:
namespace Study
{
//这个都不需要了
//public delegate int AddDelegate(int x, int y);
internal class UseMethodDelegate
{
/// <summary>
/// 为了方便演示,直接定义了静态方法,注意:这里的委托参数当然可以跟前面一样在构造函数里面初始化
/// </summary>
public static int Add(int x, int y, Func<int, int, int> delegateAdd)
{
if (delegateAdd != null)
{
return delegateAdd(x, y);
}
return 0;
}
}
class UseApp
{
static void Main()
{
int result = UseMethodDelegate.Add(10, 20, (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]