深入理解C#(十一)

*第五章(C# 2:进入快速通道的委托(上))

回顾C# 1中我们的做法

总是先定义事件/委托实际要处理的方法,如:

1
2
3
4
static void LogKeyEvent(object sender, KeyPressEventArgs e)
{
Console.WriteLine("LogKey");
}

然后new一个新的事件/委托实例,把这个方法加到委托列表中,如:

1
button.KeyPress += new KeyPressEventHandler(LogKeyEvent);

我们总是要把委托实例实际执行的代码做参数穿给委托实例,这样绕弯子会使代码难以阅读,且使得类中填充了大量只用于委托的方法。

方法组转换

C# 2支持从方法组到一个兼容委托类型的隐式转换。方法组(method group)其实就是一个方法名,可以选择添加一个目标,也就是说和C# 1中创建委托实例使用的表达式相同(含义不同,一个是类型加参数,一个是方法组)。新的隐式转换:

1
button.KeyPress+=LogkeyEvent;

一个创建线程的代码:

1
Thread t=new Thread(MyMethod);

为什么表达式如LogKeyEvent属于方法组,因为如果有重载的话,可能不止一个方法适用。隐式转换会将一个方法组转换为具有兼容签名的任意委托类型。

假定有以下两个方法签名:

1
2
void MyMethod()
void MyMethod(object sender,EventArgs e)

那么在向一个ThreadStart或EventHandler赋值时,都可以将MyMethod作为方法组使用:

1
2
ThreadStart x=MyMethod;
EventHandler y=MyMethod;

对于本身已重载成可以获取一个ThreadStart或EventHandler的方法,不能把它作为方法的参数使用。同样,不能利用隐式方法组转换来转换成普通的System.Delegate类型。可用辅助方法、强制转换或中间变量来解决。

协变性和逆变性

委托参数的逆变性

举例说明:

1
2
3
public delegate void EventHandler(object sender,EventArgs e)
public delegate void KeyPressEventHandler(object sender, KeyPressEventArgs e)
public delegate void MouseEventHandler(object sender,MouseEventArgs e)

有三个委托类型的签名:KeyPressEventArgs和MouseEventArgs都是从EventArgs派生
利用方法组转换和委托逆变性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static void LogPlainEvent(object sender, EventArgs e)
{
Console.WriteLine ("An event occurred");
}

static void Main()
{
Button button = new Button();
button.Text = "Click me";
button.Click += LogPlainEvent;
button.KeyPress += LogPlainEvent;
button.MouseClick += LogPlainEvent;

Form form = new Form();
form.AutoSize = true;
form.Controls.Add(button);
Application.Run(form);
}

用一个事件处理方法来处理所有事件。

关于事件处理方法的参数说明:

第一个参数是object类型,代表事件来源;第二个参数则负责携带与事件有关的任何额外信息

在有委托参数协变性后,我们可以使用一个具有EventHandler签名的方法,作为符合约定的所有委托类型的操作。

委托返回类型的协变性

举例:

首先声明一个委托类型

1
delegate Stream StreamFactory();

然后声明一个方法返回一个特定的流类型。

1
2
3
4
5
6
7
8
9
static MemoryStream GenerateSampleData()
{
byte[] buffer = new byte[16];
for (int i = 0; i < buffer.Length; i++)
{
buffer[i] = (byte)i;
}
return new MemoryStream(buffer);
}

利用协变性转换方法组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void Main()
{
StreamFactory factory = GenerateSampleData;

using (Stream stream = factory())
//调用委托
{
int data;
while ((data = stream.ReadByte()) != -1)
{
Console.WriteLine(data);
}
}
Console.Read();
}

注意:委托的返回类型是stream
,但声明的方法的返回类型是MemoryStream。

StreamFactory factory = GenerateSampleData;这句话用到了方法组的转换,并利用返回类型的协变性来允许GenerateSampleData用于StreamFactory,等到调用委托实例时,实际返回的是委托声明的类型,也就是说返回类型已从MemoryStream协变成stream

利用协变性和逆变性,还可以基于一个委托实例来构造另一个委托实例。

1
2
EventHandler general=new EventHandler(HandleEvent);
KeyPressEventHandler key=new KeyPressEventHandler(general);

不兼容的风险

可能发生在派生类中。