2006年08月16日

AH 亚 CHEUNG 蒋 CHIU 肖 CHUI 隋 FO 科 HAU 孝 HON 侃 KAM 琴 KIU 娇 KWING 炯 LIN 连 LUN 纶 MUK 牧 PANG 彭 SEI 四 SIN 羡 SZE 施 TO 都 WAI 苇 WU 胡 YEE 椅 YING 蛮 YUEN 琬
AH 雅 CHEUNG 锵 CHIU 招 CHUI 翠 FOG 霍 HAU 侯 HON 汉 KAM 锦 KIU 桥 KWOK 国 LIN 莲 LUN 麟 MUNG 梦 PANG 鹏 SEK 石 SIN 冼 SZE 施 TO 滔 WAI 维 WU 湖 YEE 贻 YING 礽 YUEN 菀
AU 区 CHI 子 CHIU 俏 CHUI 趣 FOK 霍 HAU 厚 HON 翰 KAM 鑫 KIU 翘 KWOK 郭 LING 令 LUNG 隆 NAM 男 PAT 毕 SHAN 珊 SIN 僊 SZE 斯 TO 道 WAI 慧 WU 护 YEE 意 YING 滢 YUET 乙
AU 欧 CHI 之 CHIU 昭 CHUM 覃 FONG 方 HAU 校 HON 韩 KAN 芹 KIU 荞 KWOK 郭 LING 泠 LUNG 浓 NAM 南 PAU 包 SHE 畲 SIN 蒨 SZE 丝 TO 图 WAI 慰 WUI 会 YEE 义 YING 潆 YUET 月
BIK 碧 CHI 次 CHIU 钊 CHUN 俊 FONG 芳 HEI 希 HON 瀚 KAN 根 KO 高 KWONG 光 LING 玲 LUNG 龙 NAM 岚 PEI 丕 SHEK 石 SING 升 SZE 诗 TO 涛 WAI 纬 WUN 桓 YEE 绮 YIP 业 YUET 玥
BIK 璧 CHI 池 CHIU 钏 CHUN 津 FOO 火 HEI 晞 HONG 匡 KAN 勤 KO 高 KWONG 广 LING 苓 LUNG 龙 NAM 楠 PIK 碧 SHEK 石 SING 成 SZE 锶 TO 韬 WAI 蔚 WUT 屈 YEE 仪 YIP 叶 YUET 悦
BING 丙 CHI 志 CHIU 朝 CHUN 珍 FOO 伙 HEI 喜 HONG 航 KAN 简 KOK 铬 KWONG 邝 LING 凌 MA 马 NANG 能 PIK 璧 SHEK 硕 SING 承 TAI 大 TONG 唐 WAI 卫 YAM 任 YEE 谊 YIP 叶 YUET 越
BING 冰 CHI 枝 CHIU 超 CHUN 振 FOO 符 HEI 器 HONG 康 KAN 谨 KON 干 LAI 黎 LING 凌 MA 马 NAR 娜 PIN 卞 SHEUNG 尚 SING 升 TAI 弟 TONG 棠 WAI 蕙 YAM 钦 YEE 颐 YIU 姚 YUET 粤
BING 秉 CHI 知 CHIU 照 CHUN 晋 FOOK 服 HEI 熹 HONG 康 KANG 更 KONG 江 LAI 赖 LING 羚 MAK 麦 NEI 妮 PING 平 SHEUNG 湘 SING 星 TAI 邸 TONG 汤 WAI 怀 YAM 荫 YEE 懿 YIU 姚 YUET 钺
BING 炳 CHI 芝 CHIU 赵 CHUN 浚 FOOK 福 HEI 羲 HOU 侯 KANG 镜 KONG 江 LAI 励 LING 翎 MAN 文 NEUNG 娘 PING 屏 SHEUNG 嫦 SING 升 TAI 娣 TSAM 沁 WAI 炜 YAN 人 YEE 苡 YIU 尧 YUI 裔
BIT 必 CHI 芷 CHIU 潮 CHUN 真 FOON 宽 HEI 禧 HSU 许 KAR 贾 KONG 刚 LAI 礼 LING 聆 MAN 文 NG 五 PING 炳 SHEUNG 裳 SING 圣 TAI 泰 TSANG 曾 WAI 玮 YAN 仁 YEI 熙 YIU 瑶 YUI 锐
BONG 邦 CHI 姿 CHIU 霄 CHUN 秦 FOON 欢 HEI 曦 HSUI 许 KAU 九 KONG 港 LAI 丽 LING 钤 MAN 民 NG 伍 PING 萍 SHEUNG 双 SING 声 TAI 带 TSANG 增 WAI 鏸 YAN 因 YEN 殷 YIU 娆 YUI 蕊
BUN 斌 CHI 祉 CHO 祖 CHUN 竣 FORK 霍 HEI 浠 HUANG 黄 KAU 球 KOO 古 LAI 豊 LING 铃 MAN 汶 NG 伍 PING 骋 SHI 仕 SIT 薛 TAI 棣 TSE 谢 WAN 尹 YAN 昕 YEUK 约 YIU 曜 YUI 叡
CHAI 仔 CHI 致 CHO 曹 CHUN 进 FU 芙 HEUNG 向 HUEN 萱 KEI 其 KOON 冠 LAI 鹂 LING 领 MAN 曼 NG 吴 PING 苹 SHIH 施 SIU 小 TAI 戴 TSIM 詹 WAN 尹 YAN 欣 YEUK 若 YIU 耀 YUK 玉
CHAI 齐 CHI 戚 CHO 袓 CHUN 隽 FU 符 HEUNG 香 HUEN 禤 KEI 奇 KOON 观 LAM 林 LING 龄 MAN 问 NG 梧 PIU 标 SHING 成 SIU 少 TAK 特 TSO 灶 WAN 允 YAN 胤 YEUNG 洋 YIU 饶 YUK 旭
CHAI 齐 CHI 梓 CHOI 才 CHUN 榛 FU 傅 HEUNG 香 HUI 许 KEI 祈 KOT 葛 LAM 林 LING 灵 MAN 敏 NGA 雅 PO 布 SHING 成 SIU 兆 TAK 得 TSO 曹 WAN 芸 YAN 恩 YEUNG 扬 YOUNG 杨 YUK 育
CHAK 翟 CHI 智 CHOI 再 CHUN 臻 FU 富 HIM 谦 HUI 昫 KEI 纪 KU 古 LAM 淋 LIP 聂 MAN 雯 NGAI 艾 PO 步 SHING 城 SIU 邵 TAK 德 TSOI 蔡 WAN 云 YAN 殷 YEUNG 阳 YU 予 YUK 毓
CHAK 泽 CHI 紫 CHOI 材 CHUN 骏 FUI 奎 HIN 衍 HUNG 孔 KEI 基 KUA 瓜 LAM 琳 LIT 烈 MAN 万 NGAI 倪 PO 波 SHING 盛 SIU 笑 TAM 谈 TSUI 徐 WAN 温 YAN 殷 YEUNG 阳 YU 如 YUK 煜
CHAM 湛 CHI 慈 CHOI 采 CHUN 椿 FUK 褔 HIN 轩 HUNG 孔 KEI 淇 KUEN 娟 LAM 霖 LIU 廖 MAN 旻 NGAI 毅 PO 保 SHING 胜 SIU 绍 TAM 谭 TSUI 崔 WAN 运 YAN 茵 YEUNG 杨 YU 宇 YUK 淯
CHAN 陈 CHI 志 CHOI 财 CHUN 蓁 FUNG 丰 HIN 宪 HUNG 洪 KEI 期 KUEN 权 LAM 临 LO 劳 MANG 孟 NGAI 霓 PO 宝 SHING 诚 SIU 诏 TAM 谭 TUEN 段 WAN 环 YAN 寅 YEUNG 杨 YU 汝 YUK 钰
CHAN 灿 CHI 赐 CHOI 彩 CHUNG 仲 FUNG 风 HIN 献 HUNG 洪 KEI 棋 KUI 巨 LAM 蓝 LO 鲁 MANG 萌 NGAI 魏 POK 博 SHING 铖 SIU 肇 TAN 丹 TUEN 端 WAN 韵 YAN 殷 YI 依 YU 羽 YUNG 用
CHAN 璨 CHI 炽 CHOI 载 CHUNG 冲 FUNG 峰 HIN 骞 HUNG 红 KEI 琪 KUI 居 LAN 兰 LO 卢 MAO 茂 NGAI 艺 POK 璞 SHIU 萧 SIU 韶 TAN 陈 TUNG 冬 WAN 蕴 YAN 甄 YI 尔 YU 余 YUNG 勇
CHAN 镇 CHIANG 张 CHOI 蔡 CHUNG 宗 FUNG 烽 HIN 显 HUNG 虹 KEI 琦 KUI 渠 LAP 立 LOI 来 MAR 马 NGAN 晏 PONG 庞 SHP 十 SIU 啸 TANG 邓 TUNG 同 WAN 薀 YAN 忻 YICK 易 YU 雨 YUNG 容
CHAN 赞 CHICK 戚 CHOI 赛 CHUNG 忠 FUNG 逢 HING 卿 HUNG 雄 KEI 祺 KUI 驹 LARM 蓝 LOK 恪 MEI 眉 NGAN 雁 POO 布 SHU 书 SIU 萧 TAO 杜 TUNG 彤 WANG 王 YANG 杨 YIK 亦 YU 俞 YUNG 翁
CHAN 瓒 CHIGN 净 CHOK 作 CHUNG 松 FUNG 冯 HING 庆 HUNG 熊 KEI 旗 KUI 举 LAU 柳 LOK 洛 MEI 美 NGAN 韧 POON 本 SHU 舒 SIU 劭 TAT 达 TUNG 东 WANG 宏 YAO 邱 YIK 易 YU 昱 YUNG 雍
CHANG 郑 CHIK 戚 CHONG 壮 CHUNG 重 FUNG 冯 HING 罄 HUNG 熊 KEI 玑 KUI 琚 LAU 流 LOK 乐 MEI 媚 NGAN 银 POON 潘 SHU 树 SO 素 TAU 土 TUNG 桐 WANG 泓 YAT 日 YIK 易 YU 峪 YUNG 榕
CHAT 七 CHIK 绩 CHONG 庄 CHUNG 从 FUNG 枫 HING 馨 HUNG 鸿 KEI 锜 KUK 公 LAU 刘 LOK 诺 MEI 微 NGAN 颜 POON 潘 SHUE 舒 SO 苏 TAU 窦 TUNG 通 WANG 纮 YAT 逸 YIK 奕 YU 茹 YUNG 蓉
CHAU 舟 CHIK 积 CHONG 庄 CHUNG 颂 FUNG 凤 HIP 协 HWANG 黄 KEI 麒 KUK 局 LAU 鎏 LOK 骆 MEI 薇 NGAN 颜 PUI 沛 SHUEN 孙 SO 苏 TIM 添 TUNG 栋 WAT 屈 YAT 溢 YIK 益 YU 庾 YUNG 融
CHAU 周 CHIN 前 CHONG 创 CHUNG 诵 FUNG 锋 HIU 晓 I 漪 KEI 娸 KUK 谷 LAW 罗 LONG 朗 MEI 镁 NGAU 牛 PUI 佩 SHUK 淑 SUEN 孙 TIM 甜 TUNG 童 WING 永 YAU 友 YIK 亿 YU 御 YUNG 晹
CHAU 洲 CHIN 展 CHOR 佐 CHUNG 聪 FUNG 丰 HO 可 IP 叶 KEI 颀 KUK 菊 LEE 李 LONG 塱 MIN 冕 NGO 娥 PUI 佩 SHUM 岑 SUEN 楦 TIN 天 TUNG 筒 WING 泳 YAU 尤 YIK 翼 YU 愉 ZHANG 张 
CHAU 秋 CHIN 钱 CHOR 初 CHUNG 锺 HING 兴 HO 好 JIM 詹 KEUNG 姜 KUN 贯 LEI 利 LOO 卢 MING 名 NGO 敖 PUI 培 SHUM 沈 SUEN 璇 TIN 田 TUNG 董 WING 咏 YAU 有 YIM 严 YU 渝  
CHAU 邹 CHIN 钱 CHOR 楚 CHUNG 锺 HA 夏 HO 何 KA 加 KEUNG 强 KUNG 功 LEI 理 LOOK 陆 MING 明 NGO 傲 PUI 裴 SHUN 信 SUET 雪 TIN 田 TUNG 董 WING 荣 YAU 佑 YIM 艳 YU 榆  
CHEN 陈 CHIN 芊 CHOR 础 CHUNG 琮 HA 夏 HO 河 KA 圻 KHOO 古 KUNG 恭 LEI 莉 LOONG 龙 MING 明 NGON 岸 PUI 钡 SHUN 纯 SUI 水 TIN 钿 TUNG 腾 WING 荣 YAU 攸 YIN 卉 YU 瑜  
CHENG 郑 CHING 正 CHOW 周 CHUNG 璁 HA 霞 HO 浩 KA 家 KIM 俭 KUNG 恭 LEI 奶 LOW 卢 MING 铭 NIE 乃 PUN 彬 SHUN 淳 SUI 萃 TING 丁 TYE 戴 WING 颖 YAU 邱 YIN 妍 YU 虞  
CHEONG 张 CHING 呈 CHOW 邹 DIK 迪 HAN 闲 HO 荷 KA 嘉 KIM 剑 KUNG 龚 LEONG 梁 LUEN 联 MING 鸣 NIN 年 PUN 潘 SHUN 舜 SUI 瑞 TING 丁 UNG 莺 WO 和 YAU 幽 YIN 言 YU 裕  
CHEONG 章 CHING 青 CHOY 蔡 DIK 荻 HAN 娴 HO 皓 KA 珈 KIN 建 KUO 古 LEUNG 良 LUEN 銮 MIU 妙 NING 宁 PUN 嫔 SHUN 顺 SUI 穗 TING 廷 VONG 黄 WO 胡 YAU 柔 YIN 彦 YU 余  
CHEUK 灼 CHING 政 CHU 朱 DIU 吊 HANG 行 HO 贺 KAI 佳 KIN 健 KWAI 贵 LEUNG 亮 LUEN 鸾 MIU 苗 NING 柠 PUN 滨 SHUN 逊 SUM 心 TING 定 WAH 华 WON 旺 YAU 佑 YIN 然 YU 儒  
CHEUK 卓 CHING 贞 CHU 柱 FAT 发 HANG 亨 HO 豪 KAI 契 KIN 坚 KWAI 贵 LEUNG 梁 LUET 律 MIU 苗 NUI 女 SAI 世 SI 士 SUM 沈 TING 延 WAH 桦 WONG 王 YAU 游 YIN 贤 YUE 俞  
CHEUK 卓 CHING 情 CHU 珠 FA 花 HANG 杏 HO 濠 KAI 桂 KIN 键 KWAI 湀 LEUNG 梁 LUI 吕 MO 毛 O 敖 SAI 西 SI 史 SUM 芯 TING 亭 WAH 烨 WONG 汪 YAU 优 YIN 燕 YUEN 元  
CHEUK 棹 CHING 清 CHU 曙 FAI 晖 HANG 幸 HO 颢 KAI 启 KING 劲 KWAN 君 LI 利 LUI 雷 MO 巫 OI 蔼 SAI 细 SI 自 SUM 深 TING 庭 WAH 骅 WONG 黄 YAU 筱 YIN 燃 YUEN 沅  
CHEUK 绰 CHING 晴 CHU 焌 FAI 辉 HANG 幸 HO 灏 KAI 楷 KING 景 KWAN 均 LI 李 LUI 雷 MO 武 ON 安 SAI 茜 SI 施 SUM 琛 TING 婷 WAI 位 WONG 黄 YEE 一 YING 邢 YUEN 阮  
CHEUK 焯 CHING 晶 CHUEN 川 FAN 帆 HANG 恒 HOI 海 KAI 继 KING 敬 KWAN 坤 LIANG 梁 LUI 蕾 MO 武 ON 铵 SAM 三 SI 师 SUN 申 TING 鼎 WAI 威 WONG 黄 YEE 二 YING 映 YUEN 宛  
CHEUNG 昌 CHING 程 CHUEN 中 FAN 芬 HANG 衡 HOI 凯 KAI 棨 KING 璟 KWAN 昆 LIAO 廖 LUK 六 MO 舞 OR 柯 SAM 森 SI 时 SUN 辛 TING 霆 WAI 为 WONG 煌 YEE 以 YING 盈 YUEN 袁  
CHEUNG 长 CHING 程 CHUEN 全 FAN 范 HANG 铿 HOI 开 KAK 革 KING 琼 KWAN 昆 LIEW 廖 LUK 陆 MO 慕 PAK 北 SAN 山 SIK 式 SUN 新 TING 渟 WAI 韦 WOO 胡 YEE 伊 YING 英 YUEN 婉  
CHEUNG 张 CHING 菁 CHUEN 春 FAN 勋 HANG 姮 HOI 爱 KAK 极 KING 竞 KWAN 焜 LIK 力 LUK 陆 MOK 莫 PAK 白 SANG 生 SIK 锡 SUN 燊 TIP 迭 WAI 韦 WOOD 活 YEE 圯 YING 瑛 YUEN 渊  
CHEUNG 祥 CHING 靖 CHUEN 泉 FAN 熏 HANG 珩 HOI 恺 KAM 甘 KIT 杰 KWAN 钧 LIK 历 LUK 禄 MOOK 木 PAK 百 SAU 秀 SIM 婵 SUNG 宋 TIT 铁 WAI 伟 WOON 垣 YEE 儿 YING 影 YUEN 园  
CHEUNG 掌 CHING 精 CHUEN 传 FANG 方 HANG 蘅 HOI 垲 KAM 甘 KIT 杰 KWAN 筠 LIM 林 LUM 林 MOON 满 PAK 伯 SAU 修 SIN 仙 SUNG 宋 TO 杜 WAI 尉 WOON 奂 YEE 宜 YING 莹 YUEN 源  
CHEUNG 翔 CHING 澄 CHUEN 铨 FAT 佛 HANG 恒 HOI 铠 KAM 金 KIT 结 KWAN 群 LIM 廉 LUN 伦 MUI 妹 PAK 柏 SE 畲 SIN 倩 SUNG 崇 TO 徒 WAI 帏 WOON 媛 YEE 怡 YING 凝 YUEN 远  
CHEUNG 象 CHING 静 CHUI 徐 FEI 飞 HAU 口 HOK 学 KAM 金 KIT 洁 KWAN 关 LIM 濂 LUN 伦 MUI 梅 PAK 珀 SECK 石 SIN 单 SZE 司 TO 桃 WAI 惠 WOON 焕 YEE 治 YING 应 YUEN 润  
CHEUNG 璋 CHIT 哲 CHUI 崔 FEI 菲 HAU 巧 HOK 鹤 KAM 淦 KIU 乔 KWAN 关 LIN 连 LUN 仑 MUI 梅 PANG 彭 SEE 施 SIN 善 SZE 思 TO 淘 WAI 渭 WOON 缓 YEE 倚 YING 樱 YUEN 浣  
2006年07月25日

 摘自 Programming .NET Components


 







6.2. Working with .NET Events


This section discusses .NET event-design guidelines and development practices that promote loose coupling between publishers and subscribers, improve availability, conform to existing conventions, and generally take advantage of .NET’s rich event-support infrastructure. Another event-related technique (publishing events asynchronously) is discussed in Chapter 7.


6.2.1. Defining Delegate Signatures


Although technically a delegate declaration can define any method signature, in practice, event delegates should conform to a few specific guidelines.


First, the target methods should have a void return type. For example, for an event dealing with a new value for a number, such a signature might be:

    public delegate void NumberChangedEventHandler(int number);


The reason you should use a void return type is that it simply doesn’t make sense to return a value to the event publisher. What should the event publisher do with those values? The publisher has no idea why an event subscriber wants to subscribe in the first place. In addition, the delegate class hides the actual publishing act from the publisher. The delegate is the one iterating over its internal list of sinks (subscribing objects), calling each corresponding method; the returned values aren’t propagated to the publisher’s code. The logic for using the void return type also suggests that you should avoid output parameters that use either the ref or out parameter modifiers, because the output parameters of the various subscribers don’t propagate to the publisher.


Second, some subscribers will probably want to receive the same event from multiple event-publishing sources. Because there is no flexibility in providing as many methods as publishers, the subscriber will want to provide the same method to multiple publishers. To allow the subscriber to distinguish between events fired by different publishers, the signature should contain the publisher’s identity. Without relying on generics (discussed later), the easiest way to do this is to add a parameter of type object, called the sender parameter:

    public delegate void NumberChangedEventHandler(object sender,int number);


A publisher then simply passes itself as the sender (using this in C# or Me in Visual Basic 2005).


Finally, defining actual event arguments (such as int number) couples publishers to subscribers, because the subscriber has to expect a particular set of arguments. If you want to change these arguments in the future, such a change affects all subscribers. To contain the impact of an argument change, .NET provides a canonical event arguments container, the EventArgs class, that you can use in place of a specific list of arguments. The EventArgs class definition is as follows:

    public class EventArgs
{
public static readonly EventArgs Empty;
static EventArgs( )
{
Empty = new EventArgs( );
}
public EventArgs( )
{}
}


Instead of specific event arguments, you can pass in an EventArgs object:

    public delegate void NumberChangedEventHandler(object sender,EventArgs eventArgs);


If the publisher has no need for an argument, simply pass in EventArgs.Empty, taking advantage of the static constructor and the static read-only Empty class member.


If the event requires arguments, derive a class from EventArgs, such as NumberChangedEventArgs; add member variables, methods, or properties as required; and pass in the derived class. The subscriber should downcast EventArgs to the specific argument class associated with this event (NumberChangedEventArgs, in this example) and access the arguments. Example 6-5 demonstrates the use of an EventArgs-derived class.


Example 6-5. Events arguments using an EventArgs-derived class
    public delegate void NumberChangedEventHandler(object sender,EventArgs eventArgs);

public class NumberChangedEventArgs : EventArgs
{
public int Number;//This should really be a property
}
public class MyPublisher
{
public event NumberChangedEventHandler NumberChanged;
public void FireNewNumberEvent(EventArgs eventArgs)
{
//Always check delegate for null before invoking
if(NumberChanged != null)
NumberChanged(this,eventArgs);
}
}
public class MySubscriber
{
public void OnNumberChanged(object sender,EventArgs eventArgs)
{
NumberChangedEventArgs numberArg;
numberArg = eventArgs as NumberChangedEventArgs;
Debug.Assert(numberArg != null);
string message = numberArg.Number;
MessageBox.Show(“The new number is “+ message);
}
}
//Client-side code
MyPublisher publisher = new MyPublisher( );
MySubscriber subscriber = new MySubscriber( );
publisher.NumberChanged += subscriber.OnNumberChanged;

NumberChangedEventArgs numberArg = new NumberChangedEventArgs( );
numberArg.Number = 4;

//Note that the publisher can publish without knowing the argument type
publisher.FireNewNumberEvent(numberArg);



Deriving a class from EventArgs to pass specific arguments allows you to add arguments, remove unused arguments, derive yet another class from EventArgs, and so on, without forcing changes on the subscribers that do not care about the new aspects.


Because the resulting delegate definition is now so adaptable, .NET provides the EventHandler delegate:

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


EventHandler is used extensively by the .NET application frameworks, such as Windows Forms and ASP.NET. However, the flexibility of an amorphous base class comes at the expense of type safety. To address that problem, .NET provides a generic version of the EventHandler delegate:

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


If you subscribe to the belief that all your events take the form of a sender object and an EventArgs-derived class, then this delegate can suffice for all your needs. In all other cases, you still have to define delegates for the specific signatures you have to deal with.












The convention for the subscriber’s event-handling method name is On, which makes the code standard and readable.



6.2.2. Defining Custom Event Arguments


As explained in the preceding section, you should provide arguments to an event handler in a class derived from EventArgs and have the arguments as class members. The delegate class simply iterates over its list of subscribers, passing the argument objects from one subscriber to the next. However, nothing prevents a particular subscriber from modifying the argument values and thus affecting all successive subscribers that handle the event. Usually, you should prevent subscribers from modifying these members as they are passed from one subscriber to the next. To preclude changing the argument, either provide access to the arguments as read-only properties or expose them as public members and apply the readonly access modifier. In both cases, you should initialize the argument values in the constructor. Example 6-6 shows both techniques.


Example 6-6. Preventing subscribers from modifying parameters in the argument class
    public class NumberEventArgs1 : EventArgs
{
public readonly int Number;
public NumberEventArgs1(int number)
{
Number = number;
}
}
public class NumberEventArgs2 : EventArgs
{
int m_Number;

public NumberEventArgs2(int number)
{
m_Number = number;
}
public int Number
{
get
{
return m_Number;
}
}
}



6.2.3. The Generic Event Handler


Generic delegates are especially useful when it comes to events. Assuming that all delegates used for event management should return void and have no outgoing parameters, then the only thing that distinguishes one such delegate from another is the number of arguments and their type. This difference can easily be generalized using generics. Consider this set of delegate definitions:

    public delegate void GenericEventHandler(  );
public delegate void GenericEventHandler(T t);
public delegate void GenericEventHandler(T t,U u);
public delegate void GenericEventHandler(T t,U u,V v);
public delegate void GenericEventHandler(T t,U u,V v,W w);
public delegate void GenericEventHandler(T t,U u,V v,W w,X x);
public delegate void GenericEventHandler(T t,U u,V v,W w,
X x,Y y);
public delegate void GenericEventHandler(T t,U u,V v,W w,
X x,Y y,Z z);












The technique used in the definition of GenericEventHandler is called overloading by type parameter arity. The compiler actually assigns different names to the overloaded delegates, distinguishing them based on the number of arguments or the number of generic type parameters. For example, the following code:

        Type type = typeof(GenericEventHandler<,>);
Trace.WriteLine(type.ToString( ));


traces:

        GenericEventHandler`2[T,U]
because the compiler appends `2 to the delegate name (2 being the
number of generic type parameters used).



The various GenericEventHandler versions can be used to invoke any event-handling method that accepts between zero and seven arguments (more than five or so arguments is a bad practice anyway, and you should use structures, or an EventArgs-derived class, to pass multiple arguments). You can define any combination of types using GenericEventHandler. For example:

    GenericEventHandler<int> del1;
GenericEventHandler<int,int> del2;
GenericEventHandler<int,string> del3;
GenericEventHandler<int,string,int> del4;


or, for passing multiple arguments:

    struct MyStruct
{…}
public class MyArgs : EventArgs
{…}
GenericEventHandler del5;
GenericEventHandler del6;


Example 6-7 shows the use of GenericEventHandler and a generic event-handling method.


Example 6-7. Generic event handling
    public class MyArgs : EventArgs
{…}
public class MyPublisher
{
public event GenericEventHandler MyEvent;
public void FireEvent( )
{
MyArgs args = new MyArgs(…);
MyEvent(this,args);
}
}
public class MySubscriber where A : EventArgs
{
public void OnEvent(MyPublisher sender,A args)
{…}
}
MyPublisher publisher = new MyPublisher( );
MySubscriber subscriber = new MySubscriber( );
publisher.MyEvent += subscriber.OnEvent;


This example uses GenericEventHandler with two type parameters, the sender type and the argument container class, similar to generic and non-generic versions of EventHandler. However, unlike EventHandler, GenericEventHandler is type-safe, because it accepts only objects of the type MyPublisher (rather than merely object) as senders. Clearly, you can use GenericEventHandler with all event signatures, including those that do not comply with the sender object and the EventArgs derivation guideline.


For demonstration purposes, Example 6-7 also uses a generic subscriber, which accepts a generic type parameter for the event argument container. You could define the subscriber to use a specific type of argument container instead, without affecting the publisher code at all:

    public class MySubscriber
{
public void OnEvent(MyPublisher sender,MyArgs args)
{…}
}


If you want to enforce the use of an EventArgs-derived class as an argument, you can put a constraint on GenericEventHandler:

    public delegate void GenericEventHandler(T t,U u) where U : EventArgs;


However, for the sake of generic use of GenericEventHandler, it is better to place the constraint on the subscribing type (or the subscribing method), as shown in Example 6-7.












Sometimes is it useful to alias a particular combination of specific types. You can do that via the using statement:

    using MyHandler = GenericEventHandler
;
public class MyPublisher
{
public event MyHandler MyEvent;
//Rest of MyPublisher
}


Note that the scope of aliasing is the scope of the file, so you have to repeat aliasing across the project files, in the same way you would if you were using namespaces.



6.2.4. Publishing Events Defensively


In .NET, if a delegate has no targets in its internal list, its value will be set to null. A C# publisher should always check a delegate for a null value before attempting to invoke it. If no client has subscribed to the event, the delegate’s target list will be empty and the delegate value set to null. When the publisher tries to access a nulled delegate, an exception is thrown. Visual Basic 2005 developers don’t need to check the value of the delegate because the RaiseEvent statement can accept an empty delegate. Internally, RaiseEvent checks that the delegate is not null before accessing it.


A separate problem with delegate-based events is exceptions. Any unhandled exception raised by the subscriber will be propagated to the publisher. Some subscribers may encounter an exception in their handling of the event, not handle it, and cause the publisher to crash. For these reasons, you should always publish inside a try/catch block. Example 6-8 demonstrates these points.


Example 6-8. Defensive publishing
    public class MyPublisher
{
public event EventHandler MyEvent;
public void FireEvent( )
{
try
{
if(MyEvent != null)
MyEvent(this,EventArgs.Empty);
}
catch
{
//Handle exceptions
}
}
}


However, the code in Example 6-8 aborts the event publishing in case a subscriber throws an exception. Sometimes you want to continue publishing even if a subscriber throws an exception. To do so, you need to manually iterate over the internal list maintained by the delegate and catch any exceptions thrown by the individual delegates in the list. You access the internal list by using a special method every delegate supports (see Example 6-1), called GetInvocationList( ). The method is defined as:

    public virtual Delegate[] GetInvocationList(  );


GetInvocationList( ) returns a collection of delegates you can iterate over, as shown in Example 6-9.


Example 6-9. Continuous publishing in the face of exceptions thrown by the subscribers
    public class MyPublisher
{
public event EventHandler MyEvent;
public void FireEvent( )
{
if(MyEvent == null)
{
return;
}
Delegate[] delegates = MyEvent.GetInvocationList( );
foreach(Delegate del in delegates)
{
EventHandler sink = (EventHandler)del;
try
{
sink(this,EventArgs.Empty);
}
catch{}
}
}
}


6.2.4.1 The EventsHelper class

The problem with the publishing code in Example 6-9 is that it isn’t reusableyou have to duplicate it each time you want fault isolation between the publisher and the subscribers. It’s possible, however, to write a helper class that can publish to any delegate, pass any argument collection, and catch potential exceptions. Example 6-10 shows the EventsHelper static class, which provides the static Fire( ) method. The Fire( ) method defensively fires any type of event.


Example 6-10. The EventsHelper class
    public static class EventsHelper
{
public static void Fire(Delegate del,params object[] args)
{
if(del == null)
{
return;
}
Delegate[] delegates = del.GetInvocationList( );
foreach(Delegate sink in delegates)
{
try
{
sink.DynamicInvoke(args);
}
catch{}
}
}
}


There are two key elements to implementing EventsHelper. The first is its ability to invoke any delegate. This is possible using the DynamicInvoke( ) method, which every delegate provides (see Example 6-1). DynamicInvoke( ) invokes the delegate, passing it a collection of arguments. It is defined as:

    public object DynamicInvoke(object[] args);


The second key in implementing EventsHelper is passing it an open-ended number of objects as arguments for the subscribers. This is done using the C# params parameter modifier (ParamArray in Visual Basic 2005), which allows inlining of objects as parameters. The compiler converts the inlined arguments into an array of objects and passes in that array.


Using EventsHelper is elegant and straightforward: simply pass it the delegate to invoke and the parameters. For example, for the delegate MyEventHandler, defined as:

    public delegate void MyEventHandler(int number,string str);


the following can be the publishing code:

    public class MyPublisher
{
public event MyEventHandler MyEvent;
public void FireEvent(int number, string str)
{
EventsHelper.Fire(MyEvent,number,str);
}
}


When using EventsHelper from Visual Basic 2005, you need to access the delegate directly. The Visual Basic 2005 compiler generates a hidden member variable corresponding to the event member. The name of that hidden member is the name of the event, suffixed with Event:

    Public Delegate Sub MyEventHandler (ByVal number As Integer, ByVal str As String)
Public Class MyPublisher
Public Event MyEvent As MyEventHandler
Public Sub FireEvent (ByVal number As Integer, ByVal str As String)
EventsHelper.Fire(MyEventEvent, number, str)
End Sub
End Class


6.2.4.2 Making EventsHelper type-safe

The problem with EventsHelper as presented in Example 6-10 is that it is not type-safe. The Fire( ) method takes a collection of amorphous objects to allow any combination of parameters, including an incorrect combination. For example, given this delegate definition:

    public delegate void MyEventHandler(int number,string str);


the following publishing code will compile fine but will fail to publish:

    public class MyPublisher
{
public event MyEventHandler MyEvent;
public void FireEvent(int number, string str)
{
EventsHelper.Fire(MyEvent,”Not“,”Type”,”Safe”);
}
}


Any mismatches in the number of arguments or their type will not be detected at compile time. In addition, EventsHelper will snuff out the exceptions, and you will not even be aware of the problem.


However, if you can commit to always using GenericEventHandler instead of defining your own delegates for event handling, there is a way to enforce compile-time type safety. Instead of defining the delegate like so:

    public delegate void MyEventHandler(int number,string str);


use GenericEventHandler directly, or alias it:

    using MyEventHandler = GenericEventHandler;


Next, combine EventsHelper with GenericEventHandler, as shown in Example 6-11.


Example 6-11. The type-safe EventsHelper
    public static class EventsHelper
{
//Same as Fire( ) in Example 6-10
public static void UnsafeFire(Delegate del,params object[] args)
{…}
public static void Fire(GenericEventHandler del)
{
UnsafeFire(del);
}
public static void Fire(GenericEventHandler del,T t)
{
UnsafeFire(del,t);
}
public static void Fire(GenericEventHandler del,T t,U u)
{
UnsafeFire(del,t,u);
}
public static void Fire(GenericEventHandler del,
T t,U u,V v)
{
UnsafeFire(del,t,u,v);
}
public static void Fire(GenericEventHandler del,
T t,U u,V v,W w)
{
UnsafeFire(del,t,u,v,w);
}
public static void Fire(GenericEventHandler del,
T t,U u,V v,W w,X x)
{
UnsafeFire(del,t,u,v,w,x);
}
public static void Fire(GenericEventHandler del,
T t,U u,V v,W w,X x,Y y)
{
UnsafeFire(del,t,u,v,w,x,y);
}
public static void Fire(GenericEventHandler del,
T t,U u,V v,W w,X x,Y y,Z z)
{
UnsafeFire(del,t,u,v,w,x,y,z);
}
}


Because the number and type of arguments passed to GenericEventHandler are known to the compiler, you can enforce type safety at compile time. Thanks to the overloading by the number of generic type parameters, you need not even specify any of the generic type parameters passed to the Fire( ) methods. The publishing code remains the same as if you are using the definition of Example 6-10:

    using MyEventHandler = GenericEventHandler;

public class MyPublisher
{
public event MyEventHandler MyEvent;
public void FireEvent(int number, string str)
{

//This is now type-safe
EventsHelper.Fire(MyEvent,number,str);
}
}



The compiler infers the type and number of type parameters used and selects the correct overloaded version of Fire( ).


If you can only guarantee the use of EventHandler, rather than GenericEventHandler, you can add these overloaded methods to EventsHelper:

    public static void Fire(EventHandler del,object sender,EventArgs e)
{
UnsafeFire(del,sender,e);
}
public static void Fire(EventHandler del,object sender,E e)
where E : EventArgs
{
UnsafeFire(del,sender,t);
}


This will enable you to publish defensively, in a type-safe manner, any EventHandler-based event.


EventsHelper can still offer the non-type-safe UnsafeFire( ) method:

    public static void UnsafeFire(Delegate del,params object[] args)
{
if(args.Length > 7)
{
Trace.TraceWarning(“Too many parameters. Consider a structure
to enable the use of the type-safe versions”);
}
//Rest same as in Example 6-11
}


This is required in case you are dealing with delegates that are not based on GenericEventHandler or EventHandler, or when you have more than the number of parameters GenericEventHandler can deal with. Obviously, using more than even five parameters should be avoided for any method by definition, but at least now the developer using EventsHelper is aware of the type-safety pitfall. If you want to always enforce the use of the type-safe Fire( ) methods, simply define UnsafeFire( ) as private:

    static void UnsafeFire(Delegate del,params object[] args);


6.2.5. Event Accessors


To hook up a subscriber to a publisher, you access the publisher’s event member variable directly. Exposing class members in public is asking for trouble; it violates the core object-oriented design principle of encapsulation and information hiding, and it couples all subscribers to the exact member variable definition. To mitigate this problem, C# provides a property-like mechanism called an event accessor. Accessors provide a benefit similar to that of properties, hiding the actual class member while maintaining the original ease of use. C# uses add and removeperforming the functions of the += and -= operators, respectivelyto encapsulate the event member variable. Example 6-12 demonstrates the use of event accessors and the corresponding client code. Note that when you use event accessors, there is no advantage to marking the encapsulated delegate member as an event.


Example 6-12. Using event accessors
    using MyEventHandler = GenericEventHandler;
public class MyPublisher
{
MyEventHandler m_MyEvent;
public event MyEventHandler MyEvent
{
add
{
m_MyEvent += value;
}
remove
{
m_MyEvent -= value;
}
}
public void FireEvent( )
{
EventsHelper.Fire(m_MyEvent,”Hello”);
}
}
public class MySubscriber
{
public void OnEvent(string message)
{
MessageBox.Show(message);
}
}
//Client code:
MyPublisher publisher = new MyPublisher( );
MySubscriber subscriber = new MySubscriber( );

//Set up connection:
publisher.MyEvent += subscriber.OnEvent;

publisher.FireEvent( );

//Tear down connection:
publisher.MyEvent -= subscriber.OnEvent;



6.2.6. Managing Large Numbers of Events


Imagine a class that publishes a very large number of events. This is common when developing frameworks; for example, the class Control in the System.Windows.Forms namespace has dozens of events corresponding to many Windows messages. The problem with handling numerous events is that it’s simply impractical to allocate a class member for each event: the class definition, documentation, CASE tool diagrams, and even IntelliSense would be unmanageable. To address this predicament, .NET provides the EventHandlerList class (found in the System.ComponentModel namespace):

    public sealed class EventHandlerList : IDisposable
{
public EventHandlerList( );
public Delegate this[object key]{get;set;}
public void AddHandler(object key, Delegate value);
public void AddHandlers(EventHandlerList listToAddFrom);
public void RemoveHandler(object key, Delegate value);
public virtual void Dispose( );
}


EventHandlerList is a linear list that stores key/value pairs. The key is an object that identifies the event, and the value is an instance of System.Delegate. Because the index is an object, it can be an integer index, a string, a particular button instance, and so on. You add and remove individual event-handling methods using the AddHandler and RemoveHandler methods, respectively. You can also add the content of an existing EventHandlerList, using the AddHandlers( ) method. To fire an event, you access the event list using the indexer with the key object, and you get back a System.Delegate object. You then downcast that delegate to the actual event delegate and fire the event.


Example 6-13 demonstrates using the EventHandlerList class when implementing a Windows Forms-like button class called MyButton. The button supports many events, such as mouse click and mouse move, all channeled to the same event list. Using event accessors, this is completely encapsulated from the clients.


Example 6-13. Using the EventHandlerList class to manage a large number of events
    using System.ComponentModel;

using ClickEventHandler = GenericEventHandler;
using MouseEventHandler = GenericEventHandler;

public class MyButton
{
EventHandlerList m_EventList;
public MyButton( )
{
m_EventList = new EventHandlerList( );
/* Rest of the initialization */
}
public event ClickEventHandler Click
{
add
{
m_EventList.AddHandler(“Click”,value);
}
remove
{
m_EventList.RemoveHandler(“Click”,value);
}
}
public event MouseEventHandler MouseMove
{
add
{
m_EventList.AddHandler(“MouseMove”,value);
}
remove
{
m_EventList.RemoveHandler(“MouseMove”,value);
}
}
void FireClick( )
{
ClickEventHandler handler = m_EventList["Click"] as ClickEventHandler;
EventsHelper.Fire(handler,this,EventArgs.Empty);
}
void FireMouseMove(MouseButtons button,int clicks,int x,int y,int delta)
{
MouseEventHandler handler = m_EventList["MouseMove"] as MouseEventHandler;
MouseEventArgs args = new MouseEventArgs(button,clicks,x,y,delta);
EventsHelper.Fire(handler,this,args);
}
/* Other methods and events definition */
}



The problem with Example 6-13 is that events such as mouse move or even mouse click are raised frequently, and creating a new string as a key for each invocation increases the pressure on the managed heap. A better approach would be to use pre-allocated static keys, shared among all instances:

    public class MyButton
{
EventHandlerList m_EventList;
static object m_MouseMoveEventKey = new object( );

public event MouseEventHandler MouseMove
{
add
{
m_EventList.AddHandler(m_MouseMoveEventKey,value);
}
remove
{
m_EventList.RemoveHandler(m_MouseMoveEventKey,value);
}
}
void FireMouseMove(MouseButtons button,int clicks,int x,int y,int delta)
{
MouseEventHandler handler;
handler = m_EventList[m_MouseMoveEventKey] as MouseEventHandler;
MouseEventArgs args = new MouseEventArgs(button,clicks,x,y,delta);
EventsHelper.Fire(handler,this,args);
}
/* Rest of the implementation */
}



6.2.7. Writing Sink Interfaces


By hiding the actual event members, event accessors provide barely enough encapsulation. However, you can improve on this model. To illustrate, consider the case where a subscriber wishes to subscribe to a set of events. Why should it make multiple potentially expensive calls to set up and tear down the connections? Why does the subscriber need to know about the event accessors in the first place? What if the subscriber wants to receive events on an entire interface, instead of individual methods? The next step is to provide a simple but generic way to manage the connections between the publisher and the subscribersone that will save the redundant calls, encapsulate the event accessors and members, and allow sinking interfaces. This section describes a technique I have developed to do just that. Consider an interface that defines a set of events, the IMySubscriber interface:

    public interface IMySubscriber
{
void OnEvent1(object sender,EventArgs eventArgs);
void OnEvent2(object sender,EventArgs eventArgs);
void OnEvent3(object sender,EventArgs eventArgs);
}


Anybody can implement this interface, and the interface is really all the publisher should know about:

    public class MySubscriber : IMySubscriber
{
public void OnEvent1(object sender,EventArgs eventArgs)
{…}
public void OnEvent2(object sender,EventArgs eventArgs)
{…}
public void OnEvent3(object sender,EventArgs eventArgs)
{…}
}


Next, define an enumeration of the events and mark the enum with the Flags attribute:

    [Flags]
public enum EventType
{
OnEvent1,
OnEvent2,
OnEvent3,
OnAllEvents = OnEvent1|OnEvent2|OnEvent3
}


The Flags attribute indicates that the enum values could be used as a bit mask (see the EventType.OnAllEvents definition). This allows you to combine different enum values using the | (OR) bitwise operator or mask them using the & (AND) operator.


The publisher provides two methods, Subscribe( ) and Unsubscribe( ), each of which accepts two parameters: the interface and a bit-mask flag to indicate which events to subscribe the sink interface to. Internally, the publisher can have an event delegate member per method on the sink interface, or just one for all methods (it’s an implementation detail, hidden from the subscribers). Example 6-14 uses an event member variable for each method on the sink interface. It shows Subscribe( ) and Un Subscribe( ), as well as the FireEvent( ) method, with error handling removed for clarity. Subscribe( ) checks the flag and subscribes the corresponding interface method:

    if((eventType & EventType.OnEvent1) == EventType.OnEvent1)
{
m_Event1 += subscriber.OnEvent1;
}


UnSubscribe( ) removes the subscription in a similar fashion.


Example 6-14. Sinking interfaces
    using MyEventHandler = GenericEventHandler;
public class MyPublisher
{
MyEventHandler m_Event1;
MyEventHandler m_Event2;
MyEventHandler m_Event3;

public void Subscribe(IMySubscriber subscriber,EventType eventType)
{
if((eventType & EventType.OnEvent1) == EventType.OnEvent1)
{
m_Event1 += subscriber.OnEvent1;
}
if((eventType & EventType.OnEvent2) == EventType.OnEvent2)
{
m_Event2 += subscriber.OnEvent2;
}
if((eventType & EventType.OnEvent3) == EventType.OnEvent3)
{
m_Event3 += subscriber.OnEvent3;
}
}
public void Unsubscribe(IMySubscriber subscriber,EventType eventType)
{
if((eventType & EventType.OnEvent1) == EventType.OnEvent1)
{
m_Event1 -= subscriber.OnEvent1;
}
if((eventType & EventType.OnEvent2) == EventType.OnEvent2)
{
m_Event2 -= subscriber.OnEvent2;
}
if((eventType & EventType.OnEvent3) == EventType.OnEvent3)
{
m_Event3 -= subscriber.OnEvent3;
}
}
public void FireEvent(EventType eventType)
{
if((eventType & EventType.OnEvent1) == EventType.OnEvent1)
{
EventsHelper.Fire(m_Event1,this,EventArgs.Empty);
}
if((eventType & EventType.OnEvent2) == EventType.OnEvent2)
{
EventsHelper.Fire(m_Event2,this,EventArgs.Empty);
}
if((eventType & EventType.OnEvent3) == EventType.OnEvent3)
{
EventsHelper.Fire(m_Event3,this,EventArgs.Empty);
}
}
}



The code required for subscribing or unsubscribing is equally straightforward:

    MyPublisher publisher = new MyPublisher(  );
IMySubscriber subscriber = new MySubscriber( );
//Subscribe to events 1 and 2
publisher.Subscribe(subscriber,EventType.OnEvent1|EventType.OnEvent2);
//Fire just event 1
publisher.FireEvent(EventType.OnEvent1);


Still, it shows the elegance of this approach for sinking whole interfaces with one call, and shows how completely encapsulated the actual event class members are.







 


 

本篇文章使用aigaogao Blog软件发布, “我的Blog要备份”

2006年07月20日

Nondeterministic Destruction

Here are two rules for avoiding the NDD blues. The first rule is for programmers who use (rather than write) classes that encapsulate file handles and other unmanaged resources. Most such classes implement a method named Close or Dispose that releases resources that require deterministic closure. If you use classes that wrap unmanaged resources, call Close or Dispose on them the moment you’re finished using them. Assuming File implements a Close method that closes the encapsulated file handle, here’s the right way to use the File class:


File file = new File (“Readme.txt”);
  .
  .
  .
// Finished using the file, so close it
file.Close ();

The second rule, which is actually a set of rules, applies to developers who write classes that wrap unmanaged resources. Here’s a summary:




  • Implement a protected Dispose method (hereafter referred to as the “protected Dispose”) that takes a Boolean as a parameter. In this method, free any unmanaged resources (such as file handles) that the class encapsulates. If the parameter passed to the protected Dispose is true, also call Close or Dispose (the public Dispose inherited from IDisposable) on any class members (fields) that wrap unmanaged resources.



  • Implement the .NET Framework’s IDisposable interface, which contains a single method named Dispose that takes no parameters. Implement this version of Dispose (the “public Dispose”) by calling GC.SuppressFinalize to prevent the garbage collector from calling Finalize, and then calling the protected Dispose and passing in true.



  • Override Finalize. Finalize is called by the garbage collector when an object is “finalized”—that is, when an object is destroyed. In Finalize, call the protected Dispose and pass in false. The false parameter is important because it prevents the protected Dispose from attempting to call Close or the public Dispose on any encapsulated class members, which may already have been finalized if a garbage collection is in progress.



  • If it makes sense semantically (for example, if the resource that the class encapsulates can be closed in the manner of a file handle), implement a Close method that calls the public Dispose.


Based on these principles, here’s the right way to implement a File class:


class File : IDisposable
{
    protected IntPtr Handle = IntPtr.Zero;

    public File (string name)
    {
        // TODO: Open the file and copy the handle to Handle.
    }

    ~File ()
    {
        Dispose (false);
    }

    public void Dispose ()
    {
        GC.SuppressFinalize (this);
        Dispose (true);
    }

    protected virtual void Dispose (bool disposing)
    {
        // TODO: Close the file handle.
        if (disposing) {
            // TODO: If the class has members that wrap
            // unmanaged resources, call Close or Dispose on
            // them here.
        }
    }

    public void Close ()
    {
        Dispose ();
    }
}


Note that the “destructor”—actually, the Finalize method—now calls the protected Dispose with a false parameter, and that the public Dispose calls the protected Dispose and passes in true. The call to GC.SuppressFinalize is both a performance optimization and a measure to prevent the handle from being closed twice. Because the object has already closed the file handle, there’s no need for the garbage collector to call its Finalize method. It’s still important to override the Finalize method to ensure proper disposal if Close or Dispose isn’t called.

本篇文章使用aigaogao Blog软件发布, “我的Blog要备份”

2006年06月03日

进攻基本键位
常规移动 方向键
射门 D
短传 S
长传 A
直塞 W
加速 按住E
呼唤队友 按住Q
速度控制 按住C
假动作 Z
战术策略 小键盘
接球动作 Shift+箭头
心态 小键盘上的5


防守基本键位
常规移动 方向键
抢截 D
转换球员 S
铲球 A
守门员出击 按住W
低速防守 按住C
加速 按住E
呼唤第二个队友协防 按住Q
战术策略 按住Q+小键盘
紧逼对方球员 按住D


射门
低平射门 轻点D
高的弧线射门 按住D
吊射 按住Q+D
假射 D然后Z
头球射门 当球在头部高度的空中时按住D
凌空(半凌空)射门 当球在膝盖高度的空中时按住D


传球
短传 轻点S
长传 按住A
直塞 W
高球直塞 按住Q+W
传中 边路时按住A
地面传中 边路时双击A
低平传中 边路时按住Q+A
沿着地面快速传中 按住Q+双击A
假短传 S然后Z
假长传 A然后Z
漏球给自己 轻点Z在接球前
漏球给队友 按住Z在接球前
头球摆渡(短距离) 当球在头部高度的空中时轻点S
头球摆渡(长距离) 当球在头部高度的空中时按住A
凌空传球(短距离) 当球在膝盖高度的空中时轻点S
凌空传球(长距离) 当球在膝盖高度的空中时按住A
二过一 传球时按住Q使传球队员继续向前跑
过人动作(正常速度)
踩单车 Shift+球员面对方向
急停 Shift+球员面对方向的反方向
横向移动 Shift+球员面对方向的侧方向
360度旋转 Shift+球员面对方向的侧方向,然后向球员面对方向旋转90度
踩单车&改变方向 Shift+球员面对方向,然后点击球员面对方向的侧方向
假动作&改变方向 Shift+球员面对方向的侧方向,然后点击相反的方向


过人动作(加速中)
大步趟球 Shift+球员面对方向
改变方向 Shift+要改变的方向


过人动作(站立状态)
原地踩单车 Shift+球员面对方向的反方向
原地假动作 Shift+球员面对方向的侧方向
原地假射 Shift+球员面对方向


直接任意球
射门 按住D
短传 按住S
长传 按住A(感觉没有这个吧)
呼唤队友配合 E(感觉是C)
射门力量大一点/路线低一点 方向键下
射门力量小一点/路线高一点 方向键上
增加弧度 Shift+方向键左/右
目标 方向键左/右


间接任意球/角球
短传 按住S
长传 按住A
目标 方向键左/右
直线长传 按住Q+A


守门员
守门员出击 按住W
放下球 轻点W
大脚发球 D或者A
手抛球 S
沿着地面快速传中 按住Q+双击A


 

本篇文章使用aigaogao Blog软件发布, “我的Blog要备份”

2006年05月27日
转自 http://bbs.ee.ntu.edu.tw/boards/Taiwanese/14/1/29.html

發信人: bong.bbs@bbs.cs.nthu.edu.tw (鯤島心 台灣情), 看板: Taiwanese
標 題: 安平追想曲
發信站: 清華資訊(楓橋驛站) (Sat Jan 8 01:11:38 2000)
轉信站: Maxwell!bbs.ee.ntu!freebsd.ntu!news.cs.nthu!maple


安平追想曲 詞/陳達儒 曲/許石 1951年發表

身穿花紅長洋裝 風吹金髮思情郎
想郎船何往 音信全無通 伊是行船遇風浪
放阮情難忘 心情無地講 想思寄著海邊風 
海風無情笑阮憨 啊 不知初戀心茫茫 

想思情郎想自己 不知爹親二十年
思念想欲見 只有金十字 給阮母親做遺記
放阮私生兒 聽母初講起 愈想不幸愈哀悲
到底現在生也死 啊 伊是荷蘭的船醫

想起母子的運命 心肝想爹也怨爹
別人有爹痛 阮是母親晟 今日青春孤單影
全望多情兄 望兄的船隻 早日回歸安平城 
安平純情金小姐 啊 等你入港銅鑼聲

故事發生在三百年前的安平港,清晨六點的汽笛聲劃破了寂靜,一位
金髮飄逸的紅衣女郎,獨自在港邊走來走去,兩眼眺望著浩瀚無垠的
大海,似乎在等待著什麼?忽然見她低頭飲泣了起來,原來是想起自
己苦命的身世。民國40年,陳達儒來到台南,一時興起,登上了安平
古堡,遙望安平港之際,竟不自覺地神遊到了三百年前,被荷蘭人佔
據的安平港口,幻想著一段淒美的異國之戀。當他回過神後,立刻拿
起了筆,將所有的幻想寫成這首「安平追想曲」。陳達儒追想起這首
歌曲的緣原,曾經如此的說:「那一年,素不相識的石先生從台南市
北上來找我,希望我能為他最近寫的一首歌填詞,因為介紹人是我姑
丈的好朋友--台南市大名鼎鼎的文人許丙丁,也就不好意思推辭。」
許石的要求是填詞時不准更動這首曲子的一兩個旋律,一定要曲譜照
單全收,陳達儒想,這也許是許丙丁自己不動筆,而推薦他的原因吧
!陳達儒自認捷才,然而這次卻難倒了他,遲遲難以交稿。後來,他
和台南市籍的太太一起回娘家,在府城住了一個星期,有一天去安平
古堡遊覽,登上斑駁歷史遺跡,他在感念天地悠悠,構思了這一段可
能發生的一段異國情鴛的故事來。這次由「永續台灣文教基金會」所
舉辦的第三屆「步尋」高中生領導種子培訓營,選擇在台南安平舉辦
,所以編者也吃了不少安平小吃(所謂安平三寶 -- 蝦捲、豆花、蚵
仔煎)。另外也順便調查一下「安平追想曲」的由來,而在地人的說
法和書上的記載又有所出路,根據安平基金會的孫先生口述,在「安
平追想曲」中的金髮女子,是確實有此人,而其後代依舊居住在安平
,如此一來安平追想曲的內容,不僅是陳達儒先生所虛構的,而是真
實的故事。


2006年05月26日

把任何人在同一时间需要处理的本质复杂度的量减到最少


不要让偶然性的复杂度无谓地快速增长

本篇文章使用aigaogao Blog软件发布, “我的Blog要备份”

2006年05月24日

2006年05月19日

开发时用Java,完成后compile成JavaScript


http://code.google.com/webtoolkit/

本篇文章使用aigaogao Blog软件发布, “我的Blog要备份”

2006年05月18日

google了一下,com.bea.xml.stream.MXParserFactory在jar173.jar里,上bea下了,把里面的jsr173_1.0_ri.jar加到WEB-INF\lib,再运行,出下面的错:
org.jdom.IllegalNameException: The name “null” is not legal for >JDOM/XML DocTypes: XML names
再次头大,接着google,找到这里:http://www.caucho.com/support/resin-interest/0602/0074.html
看来是可能是Resin的原因,我用Resin3.0.18,换成Tomcat,居然成功,哈哈。不甘心,再去caucho看看,Resin出了3.0.19,下了再试,终于看到wsdl,看来可能是Resin3.0.18的问题。


接着开始写客户端,问题又来了,调用时出exception


ERROR-[2006-05-19 00:20:31,562] Fault occurred!
> java.lang.IllegalArgumentException: prefix cannot be “null” when creating a QName
>       at javax.xml.namespace.QName.<init>(QName.java:170)
>       at com.bea.xml.stream.MXParser.getName(MXParser.java:1439)


手头没有XFire的源代码,codehaus最近上不去,只好反编译,看到原来是调用QName的构造方法时prefix参数不能为null。什么意思啊?再google,有的文章说是XMLBean的QName的实现不允许prefix为空,用bea的QName的实现代替XMLBean的就可以了。反编译XBeans.jar里的QName,的确不允许prefix为空,但是应该报“invalid QName prefix”,而不是看到的prefix cannot be “null” when creating a QName,看来用的不是这个jar里的QName,用在JVM启动时加上verbose,看到原来加载的是jre\lib\rt.jar里的QName,总不会是要修改rt.jar吧,于是上jira提问,很快就收到回答


We’ll definitely take a look into this issue. It appears to be due to differences in the stax reference implementation and the woodstox stax implementation we normally use. Please try removing the stax-1.1.x-dev jar from your classpath and adding the woodstox jar from the distribution instead.


看来问题出在所用的stax的实现上面,我用的是bea的参考实现jsr173_1.0_ri.jar(受javax.xml.stream.FactoryConfigurationError: Provider com.bea.xml.stream.MXParserFactory
not found 的启发/误导),用XFire里的wstx-asl-2.9.3.jar代替,再运行,一切正常。想起XFire的Dependency Guid,里面有这段话:


Selecting a Stax Implementation (IMPORTANT!)


Stax is the XML api that powers XFire and allows it to process XML efficiently. There are many different Stax implementations out there – the reference implementation (RI), Woodstox, FastInfoset, and there is one included in the JWSDP. Regardless of which you one choose you must have the stax-api jar on your classpath.


In addition we highly recommend that you use Woodstox as your Stax implementation. It is about 30-40% faster then the RI and works more reliably. To ensure you are using Woodstox and not the RI make sure the stax-1.1.x-dev jar is off your classpath and the wstx-2.0.3 jar is on it.


嗨,都怪自己没仔细看文档,绕了个大圈。

本篇文章使用aigaogao Blog软件发布, “我的Blog要备份”

XFire 1.1 has been released. 
I wrote a simple service. The following error occured:

http://localhost:8080/TestXFire/service/MyService?wsdl

500 Servlet Exception

javax.xml.stream.FactoryConfigurationError: Provider com.bea.xml.stream.MXParserFactory
not found
at javax.xml.stream.FactoryFinder.newInstance(FactoryFinder.java:72)
at javax.xml.stream.FactoryFinder.find(FactoryFinder.java:178)
at javax.xml.stream.FactoryFinder.find(FactoryFinder.java:92)
at javax.xml.stream.XMLInputFactory.newInstance(XMLInputFactory.java:136)
at org.codehaus.xfire.util.STAXUtils.(STAXUtils.java:47)
at org.codehaus.xfire.util.jdom.StaxBuilder.(StaxBuilder.java:150)
at org.codehaus.xfire.wsdl11.builder.AbstractWSDL.(AbstractWSDL.java:50)
at org.codehaus.xfire.wsdl11.builder.DefaultWSDLBuilderFactory.createWSDLBuilder(DefaultWSDLBuilderFactory.java:25)
at org.codehaus.xfire.wsdl11.builder.WSDLBuilderAdapter.write(WSDLBuilderAdapter.java:40)
at org.codehaus.xfire.DefaultXFire.generateWSDL(DefaultXFire.java:113)
at org.codehaus.xfire.transport.http.XFireServletController.generateWSDL(XFireServletController.java:319)
at org.codehaus.xfire.transport.http.XFireServletController.doService(XFireServletController.java:140)
at org.codehaus.xfire.spring.remoting.XFireServletControllerAdapter.handleRequest(XFireServletControllerAdapter.java:63)
at org.codehaus.xfire.spring.remoting.XFireExporter.handleRequest(XFireExporter.java:44)
at org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter.handle(SimpleControllerHandlerAdapter.java:44)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:723)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:663)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:394)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:348)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:115)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:92)
at com.caucho.server.dispatch.ServletFilterChain.doFilter(ServletFilterChain.java:106)
at com.caucho.server.webapp.WebAppFilterChain.doFilter(WebAppFilterChain.java:178)
at com.caucho.server.dispatch.ServletInvocation.service(ServletInvocation.java:229)
at com.caucho.server.http.HttpRequest.handleRequest(HttpRequest.java:268)
at com.caucho.server.port.TcpConnection.run(TcpConnection.java:389)
at com.caucho.util.ThreadPool.runTasks(ThreadPool.java:492)
at com.caucho.util.ThreadPool.run(ThreadPool.java:425)
at java.lang.Thread.run(Thread.java:595)

Who can help this?

本篇文章使用aigaogao Blog软件发布, “我的Blog要备份”