Fork me on GitHub

.NET中的委托和事件(续)

 

  首先感谢大家对我博文的支持,在这篇博文中我会向大家继续介绍.NET中的委托与事件,这次是对前面的知识的进一步加深学习。首先回答一下前面各位提及到的问题。

  • 委托事件返回类型一定要为void吗?

  答:多数情况下委托封装方法都是返回void的,事实上多重委托最常见是在事件中,而.NET中对事件约定返回值为void并且二个参数分别是object和EventArgs类型的。但我们也可以实现返回值不为void委托方法,好然我们实现一个有返回值的委托。

/// <summary>
/// Define delegate and return int.
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public delegate int CalcNumber(int index);

public class PeopleCalculator
{
public static int CalcPeople1(int index)
{
return ++index;
}

public static int CalcPeople2(int index)
{
return index += 2;
}
}

  现在我们定义了一个返回值为int的委托,然后我们再初始化委托变量就OK了。下面代码实现委托变量初始化。

CalcNumber calcNumber;
calcNumber
= new CalcNumber(PeopleCalculator.CalcPeople1);
calcNumber
+= new CalcNumber(PeopleCalculator.CalcPeople2);

图1输出结果

 

  结果可以发现并没有显示2只有3,通过分析委托多播情况我们可以发现,委托根据我们初始化委托变量次序去调用具体的实现,那就说明其实方法CalcPeople1和CalcPeople2都有执行,只是最后方法把前面的结果都覆盖了。

  现在我们要获取调用的结果,所以我们必须获取委托列表,并且显式调用每个封装的方法,通过.NET中提供DelegateName.GetInvocationList方法获取委托列表代码实现如下。

foreach (CalcNumber cn in calcNumber.GetInvocationList())
{
Console.WriteLine(
"Answer is {0}.\n", cn(1));
}

  

2多播委托返回值

 

  • 怎样实现给预定义委托传递继承于EventArgs参数

  如果使用.NET控件,使其传递自定义事件关联类型,我觉得这样是不可能的因为EventArgs本身是不带任何信息,我们只能通过间接发送实现自定义事件关联类型传递,下面我将细细解释。

1.1.1匿名方法

  匿名方法就是以内联方式实现委托,接下来我们看看如何定义匿名方法

  delegate (parameters) {Implementation Code}

  定义匿名方法如上很简单,但细心的你肯定会发现怎么没有指定返回类型,其实匿名方法省去:返回类型函数名的定义,它会根据我们定义的委托的返回类型来判断匿名方法的返回类型。

////Define delegate type.
private delegate int SayNumber(int number);

  现在我们定义一个返回类型int的委托类型,和它匹配的匿名方法必需具有相同返回类型、相同参数个数及类型。

  让我们看一下如何定义一个正确的匿名函数,首先我们定义委托类型,然后再定义两个匿名方法如下:

  

 

 

 

/// <summary>
/// Define delegate type.
/// </summary>
/// <param name="number"></param>
private delegate void NumberProcessor(int number);


//// Anonymous method has parameter.
numberProcessor = delegate(int number)
{
Console.WriteLine(
"Hello I am number {0}.\n", ++number);
};

//// Anonymous method don't have any parameters.
numberProcessor += delegate
{
Console.WriteLine(
"I am fairly lazy, so I do nothing.\n");
};

  

  大家猜猜看哪个和我们定义委托类型是匹配的,要注意的是这里的委托类型返回为void,激动人心的时刻又到了是时候公布答案。





                 



3输出结果





  OK这次两个匿名方法都能和我们的委托类型匹配上,这里要注意一点是当我们的委托类型的返回值类型为void时,匿名函数匹配条件如下:

n        委托类型返回类型为void

n        匿名函数的具体实现没有使用到委托传递参数

当满足以上条件时候,委托类型的匿名方法可无参数

 

接下来让我们通过具体的代码看一下显式方法和匿名方法调用委托的区别。





 

/// <summary>
/// 使用显示方法
/// </summary>

class Program
{
////Define delegate type.
private delegate int SayNumber(int number);

//Concrete invoke method.
private static int Say(int number)
{
return ++number;
}

static void Main(string[] args)
{
SayNumber sayNumber;
////Binding delegate var to concrete method.
sayNumber = Say;
Console.WriteLine(
"Hello my number is {0}.\n", sayNumber(7));
Console.WriteLine(
"Hi my number is {0}.\n", sayNumber(1));

Console.ReadKey();
}
}

 

 

/// <summary>
/// 使用委托函数的实现
/// </summary>
class Program
{
////Define delegate type.
private delegate int SayNumber(int number);

static void Main(string[] args)
{
SayNumber sayNumber;
////Binding delegate var to concrete method.
sayNumber = delegate(int number)
{
return ++number;
};
Console.WriteLine(
"Hello my number is {0}.\n", sayNumber(7));
Console.WriteLine(
"Hi my number is {0}.\n", sayNumber(1));
Console.ReadKey();
}
}

           

                   

4输出结果

 

  也许大家觉得这样没有太大区别,只不过是去掉函数名,然后把具体实现放到委托变量实例化上面,的确我觉得这样的写法看起来丑陋,当有了λ表达式匿名方法就黯然失色了。

 

1.1.2λ表达式

  相信不用介绍很多人都了解或使用过λ表达式,特别是熟识Linq的各位。OK让我们快步进入λ表达式的介绍。

  随着C#2.0的匿名方法到C#3.0出现了λ表达式,事实上如果我们先介绍λ表达式那就没有必要再介绍匿名方法了,但是考虑到我们程序员也要前后兼容所以我们也要对匿名方法有所了解(至少我是这样认为的)。

  让我们看一下匿名方法和λ表达式表达式定义上的区别

 

              图5匿名方法和λ表达式的定义

 

  通过上面的对比我们发现匿名方法和λ表达式在定义上并没有太大的区别,匿名方法要使用关键字delegate,而λ表达式使用 =>“go to”操作符,在λ表达式中编译器将根据我们定义委托类型去推断出表达式参数类型和返回值,这使得我们可以使用更加简洁的方式去实例化委托变量。接下来我们看看λ表达式可以有多简洁吧!

 

                图6λ表达式的定义

 

  通过上图我们发现λ表达式甚至可以省去参数类型,它通过编译器推断委托类型来判断参数类型和前面提及的返回值类型推断是一样的原理。

 

  λ表达式的三部曲

  λ表达式中参数个数和类型必需和委托类型匹配

  λ表达式中的参数可以使用隐式定义,但委托类型中参数带有ref或out必需使用参数显式定义

  λ表达式如果没有传递参数,那么只需用“()”表示即可

图7λ表达式

 

1.1.3委托与事件的进阶

  在前面的博文我简单的提及了事件和委托之间的关系和实现,而且大家对于事件也再熟悉不过了特别是在windows消息机制下。大家都知道我们可以自定义委托类型,指定参数个数、类型和返回值类型。但.NET也提供我们一种已经定义好的委托类型--EventHandler 是一个预定义的委托。

//预定义委托的定义

public delegate void EventHandler(object sender, EventArgs e);

  在解析什么是预定义委托,先讲一下事件处理程序委托。

  事件处理程序委托的标准签名定义一个没有返回值的方法,其第一个参数的类型为 Object,它引用引发事件的实例,第二个参数从 EventArgs 类型派生,它保存事件数据。如果事件不生成事件数据,则第二个参数只是 EventArgs 的一个实例。否则,第二个参数为从 EventArgs 派生的自定义类型,提供保存事件数据所需的全部字段或属性。

 

图8预定义委托

 

  EventHandler 是一个预定义的委托,专用于表示不生成数据的事件的事件处理程序方法。如果事件生成数据,则必须提供自己的自定义事件数据类型,并且必须要么创建一个委托,其中第二个参数的类型为自定义类型,要么使用泛型 EventHandler 委托类并用自定义类型替代泛型类型参数。

  EventHandler 是一个预定义的委托,专用于表示不生成数据的事件的事件处理程序方法。如果事件生成数据,则必须提供自己的自定义事件数据类型,并且必须要么创建一个委托,其中第二个参数的类型为自定义类型,要么使用泛型 EventHandler 委托类并用自定义类型替代泛型类型参数。

  接下让我们实现自定义的预定义委托EventHandler<T>,首先定义一个MessageEventArgs继承于EventArgs。

 

/// <summary>
/// Custom EventArgs.
/// Include only one properity "Message".
/// </summary>
public class MessageEventArgs : EventArgs
{
private string message;

public string Message
{
get { return message; }
set { message = value; }
}

/// <summary>
/// The constructor.
/// </summary>
/// <param name="message"></param>
public MessageEventArgs(string message)
{
this.message = message;
}
}



  然后再定义一个泛型事件委托,的确很简单就可以实现一个自定义事件处理程序委托。





/// <summary>
/// Custom generic EventHandler.
/// </summary>
public static event EventHandler<MessageEventArgs> mybuttonEvent;

  现在我们已经有了事件处理程序委托了,然后我们要通过事件去调用我们的事件委托,它再去调用具体实现,这里我们通过自定义一个Button控件,然后再重写OnClick方法去调用我们自定义事件委托。

 

 

/// <summary>
/// Custom button control.
/// </summary>
public partial class WFCLButton : Button
{
public static event EventHandler<MessageEventArgs> mybuttonEvent;

public WFCLButton()
{
InitializeComponent();
}

protected override void OnClick(EventArgs e)
{
////Whether mybuttonEvent is not null, invoking event.
if (mybuttonEvent != null)
{
mybuttonEvent(
this, new MessageEventArgs("This is custom event args.\n"));
}
base.OnClick(e);
}
}



  现在我们有了自定义Button控件,而且重写了OnClick方法当发现点击时候我们就去调用自定义事件委托,现在我们往Form里面添加自定义Button控件,然后

Form的构造函数里面添加委托变量,并且绑定事件委托具体实现的方法名。





WFCL.WFCLButton.mybuttonEvent +=
new EventHandler<MessageEventArgs>(Custom_ButtonClick);





如上代码实现了事件委托和具体实现的关联,而且注意我使用的是静态事件委托。一下是事件委托具体调用方法。





private void Custom_ButtonClick(object sender, MessageEventArgs e)
{
MessageBox.Show(e.Message);
}

图9自定义事件委托

 

  这样我们就可以自定义事件处理程序委托,但细心的大家肯定发现我是通过一个间接地方法实现的,为什么这样说呢?

图10预定义委托

 

  大家看到这是什么,响应Click事件委托的是预定义委托,并不是我们自定义的事件委托,如果我们自己把EventHandler改为EventHandler<MessageEventArgs>,这时候就会出现以下错误信息。

图11自定义事件委托

 

  无法将类型“System.EventHandler<WFCL.MessageEventArgs>”隐式转换为“System.EventHandler”

  首先我们要找到是否存在泛型Click事件委托,如果没有泛型Click事件委托那我们肯定不能这样去初始化事件委托变量。我们可以在.NET Framework中的Control中找到Click的定义,里面并没有泛型定义,所为我们不能通过上面的方法实现事件委托,而是通过我上面介绍的间接方式实现事件委托。这也充分说明一点自定义委托只能通过间接手段实现(如果各位大大有直接实现方式告诉我谢谢)。

图12 Click事件委托

 

  通过上面的类图我们发现Click事件委托是在Control类中定义,而我们自定义Button控件是通过继承Button类来实现的,查看Control类Click事件委托如下,然后通过调用AddHandler来保存委托实例。

/// <summary>
/// 点击事件委托定义
/// </summary>
public event EventHandler Click
{
add
{
base.Events.AddHandler(EventClick, value);
}
remove
{
base.Events.RemoveHandler(EventClick, value);
}
}
posted @ 2011-05-01 17:18  JK_Rush  阅读(6219)  评论(9编辑  收藏  举报