在编写应用程序时,我们经常要处理这样的一组对象,它们的类型都派生自同一个基类,但又需要为每个不同的子类型应用不同的处理方法。

通常的做法,最简单的就是用很多 if-else 去判断各自的类型,如下面的代码所示(这里用 .Net 的类型系统作为例子,MethodInfo、PropertyInfo、FieldInfo 和 Type 都是 MemberInfo 的子类):

MemberInfo member;
MethodInfo methodInfo = member as MethodInfo;
if (methodInfo != null) {
// 对 methodInfo 进行处理。
} else {
PropertyInfo propertyInfo = member as PropertyInfo;
if (propertyInfo != null) {
// 对 propertyInfo 进行处理。
} else {
FieldInfo fieldInfo = member as FieldInfo;
if (fieldInfo != null) {
// 对 fieldInfo 进行处理。
} else {
Type type = member as Type;
if (type != null) {
// 对 type 进行处理。
}
}
}
}

这样写,会导致代码的缩进层数过多,非常的乱,也难以梳理其中的关系。如果在每层中添加 retuan 语句,情况会好一些,但仍然是非常复杂的,如下面的代码所示:

MemberInfo member;
MethodInfo methodInfo = member as MethodInfo;
if (methodInfo != null) {
// 对 methodInfo 进行处理。
return;
}
PropertyInfo propertyInfo = member as PropertyInfo;
if (propertyInfo != null) {
// 对 propertyInfo 进行处理。
return;
}
FieldInfo fieldInfo = member as FieldInfo;
if (fieldInfo != null) {
// 对 fieldInfo 进行处理。
return;
}
Type type = member as Type;
if (type != null) {
// 对 type 进行处理。
return;
}

总的来说,上面的方法对于比较简单的处理策略来说还是很实用的,但是对于复杂的处理来说,就有些力不从心了。

一种更好一些的办法,是将所有的处理方法及其对应的类型,都使用字典存储起来。之后就使用这个字典来根据对象类型调用相应的方法,如以下代码所示:

void ProcessMethodInfo(MemberInfo member);
void ProcessPropertyInfo(MemberInfo member);
void ProcessFieldInfo(MemberInfo member);
void ProcessType(MemberInfo member); Dictionary<Type, Action<MemberInfo>> dict = new Dictionary<Type, Action<MemberInfo>>() {
{typeof(MethodInfo), ProcessMethodInfo},
{typeof(PropertyInfo), ProcessPropertyInfo},
{typeof(FieldInfo), ProcessFieldInfo},
{typeof(Type), ProcessType},
}; MemberInfo member = null;
dict[member.GetType()](member);

这样做,代码的可读性会比较好,也更适合于比较复杂的处理。但还需要写很多额外的代码,子类型的处理方法也必须使用基类型作为参数,需要自己完成强制类型转换。但毫无疑问,这种方法将变化的部分很好的抽取了出来,都放到了字典中,维护起来也更加容易。

仿照这一方式,将委托放入字典和从字典中检索委托的过程封装起来,就形成了 Cyjb.MethodSwitcher 类,它的提供有 Create 方法,可以像下面这样使用:

void ProcessMethodInfo(MethodInfo member);
void ProcessPropertyInfo(PropertyInfo member);
void ProcessFieldInfo(FieldInfo member);
void ProcessType(Type member); Action<MemberInfo> switcher = MethodSwitcher.Create<Action<MemberInfo>>(
(Action<MethodInfo>)ProcessMethodInfo,
(Action<PropertyInfo>)ProcessPropertyInfo,
(Action<FieldInfo>)ProcessFieldInfo,
(Action<Type>)ProcessType); MemberInfo member;
switcher(member);

代码看起来并没有少多少,但它能够将字典的构造、类型的强制转换和参数类型的判断全部都封装起来,尽可能的减少了需要人工完成的部分。

首先,我认为需要切换的方法,参数应该基本是相同的,不同的应当仅仅是指示类型的参数(我将它称作关键参数)。程序可以自动识别关键参数(所有方法对应参数的类型全部不同),并提取相应的类型作为字典的键。

其次,并不是某个类型在字典中不存在,就不做处理了。而是需要沿着继承链向上查找,寻找有没有处理基类型的通用方法。这样才更加符合实际情况,特殊的子类由特殊的方法去处理,其它不怎么特殊的子类则可以由一个通用的方法去处理。如果找不到合适的处理器,则会抛出异常。

方法的查找使用了下面的方法,这个方法类似于并查集的路径压缩算法,因此基本可以看作是常数时间复杂度的。

private TDelegate GetMethodUnderlying(Type type) {
TDelegate dlg;
if (methodDict.TryGetValue(type, out dlg)) {
return dlg;
} else if (type.BaseType == null) {
return null;
} else {
dlg = GetMethodUnderlying(type.BaseType);
methodDict.Add(type, dlg);
return dlg;
}
}

最后 Create 方法返回的是一个 TDelegate 类型对象,它是利用 System.Reflection.Emit 构造得到的,尽可能的保证了执行效率。


上面的类已经可以满足简单的方法切换了,但是我还想让这个过程更加简单,甚至不愿意手动输入需要用到的方法。有没有办法让程序自动找到所有的子类型处理方法呢?在 C# 里有一个好东西可以完成这件事,那就是特性(Attribute)。

我首先定义了一个 Cyjb.ProcessorAttribute 特性,用来标记出定义的子类型处理方法(支持静态方法和实例方法)。然后,就可以使用程序来反射得到所有被标记了的方法,剩下的就与之前讲述的方法类似了。这里同样支持自动寻找关键参数,它的简单用法如下所示:

[Processor]
void ProcessMethodInfo(MethodInfo member);
[Processor]
void ProcessPropertyInfo(PropertyInfo member);
[Processor]
void ProcessFieldInfo(FieldInfo member);
[Processor]
void ProcessType(Type member); Action<MemberInfo> switcher = MethodSwitcher.Create<Action<MemberInfo>>(this); MemberInfo member;
switcher(member);

使用特性进行标注,还有一个好处就是如果添加了新的子类型处理方法,或者修改了已有的方法,已有的程序完全不需要改变,彻底的将程序变化的部分隔离了开来。

本文中提到的方法切换器类,完整的代码为 MethodSwitcher.cs

C# 方法调用的切换器 Update 2015.02.02的更多相关文章

  1. 2015/9/21 Python基础(17):绑定和方法调用

    绑定和方法调用现在我们需要再次阐述Python中绑定(binding)的概念,它主要与方法调用相关联.方法是类内部定义的函数,这意味着方法是类属性而不是实例属性.其次,方法只有在其所属的类拥有实例时, ...

  2. 文本切换器(TextSwitcher)的功能和用法

    TextSwitcher继承了ViewSwitcher,因此它具有与ViewSwitcher相同的特征:可以在切换View组件的同时使用动画效果.与ImageSwitcher相似的是,使用TextSw ...

  3. Android中使用ContentProvider进行跨进程方法调用

    原文同一时候发表在我的博客 点我进入还能看到很多其它 需求背景 近期接到这样一个需求,须要和别的 App 进行联动交互,比方下载器 App 和桌面 App 进行联动.桌面的 App 能直接显示下载器 ...

  4. struts2DMI(动态方法调用)

    struts2动态方法调用共有三种方式: 1.通过action元素的method属性指定访问该action时运行的方法 <package name="action" exte ...

  5. Hadoop中客户端和服务器端的方法调用过程

    1.Java动态代理实例 Java 动态代理一个简单的demo:(用以对比Hadoop中的动态代理) Hello接口: public interface Hello { void sayHello(S ...

  6. Struts 2之动态方法调用,不会的赶紧来

    学习Struts2框架以来为了减少Action 的数量,我们可以使用动态方法进行处理. 动态方法调用(Dynamic Method Invocation,DMI)是指表单元素的Action并不是直接等 ...

  7. struts2DMI(动态方法调用)

    DMI(Dynamic Method Invoke)即动态,是strus2的一个特性,我们知道,在最开始学习strus2时,往往一个action中只有一个excute方法,比如说add,delete, ...

  8. Android 自学之网格试图(GridView)和图片切换器(ImageSwitcher)功能和用法

    网格试图(GridView)用于在界面上按行,列分布的方式来显示多个组件. GridView和ListView有共同的父类:AbsListView,因此GridView和ListView具有一定的相似 ...

  9. UIViewController中各方法调用顺序及功能详解

    UIViewController中各方法调用顺序及功能详解 UIViewController中loadView, viewDidLoad, viewWillUnload, viewDidUnload, ...

随机推荐

  1. Loadrunner日志设置与查看

    1.打开EXtended Log Log告诉了我们一切,默认的Log是standard Log,这时远远不够的.我们要extended log,打开路径为runtime settings-->l ...

  2. 忘记mysql root用户密码

    今天帮一个售后的同事解决网盘无登录的问题,看了下后台日志,报错用密码root连接不上数据库,然后我就强行改了一下数据库密码,就OK了. (1)用root登录系统. (2)vim /etc/my.cnf ...

  3. jsp 学习 第3步 - el 自定义方法 tld 说明

    使用 el 的过程中,需要使用到后端代码处理逻辑,这个时候我们就需要自定义 方法. 如我们后端代码定义如下: package com.rhythmk.common; public class FncH ...

  4. DNS原理及其解析过程【精彩剖析】(转)

      2012-03-21 17:23:10 标签:dig wireshark bind nslookup dns 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否 ...

  5. [ruby on rails] 跟我学之(6)显示指定数据

    根据<[ruby on rails] 跟我学之路由映射>,我们知道,可以访问 GET    /posts/:id(.:format) 来显示具体的对象. 1. 修改action 修改 ap ...

  6. [BZOJ]1016 JSOI2008 最小生成树计数

    最小生成树计数 题目描述 现在给出了一个简单无向加权图.你不满足于求出这个图的最小生成树,而希望知道这个图中有多少个不同的最小生成树.(如果两颗最小生成树中至少有一条边不同,则这两个最小生成树就是不同 ...

  7. Kali Linux下破解WIFI密码挂载usb无线网卡的方法

    Kali Linux下破解WIFI密码挂载usb无线网卡的方法 时间:2014-10-12    来源:服务器之家    投稿:root 首先我要说的是,wifi密码的破解不是想象中的那么容易,目前还 ...

  8. Objective-C中的instancetype和id区别

    目录(?)[-] 有一个相同两个不同相同 Written by Mattt Thompson on Dec 10th 2012 一什么是instancetype 二关联返回类型related resu ...

  9. MogileFS 的介绍(MogileFS 系列1)[分布式文件系统]

    MogileFS 是一个开源的分布式文件系统,用于组建分布式文件集群,由 LiveJournal 旗下 Danga Interactive 公司开发,Danga 团队开发了包括 Memcached.M ...

  10. python将json格式的数据转换成文本格式的数据或sql文件

    python如何将json格式的数据快速的转化成指定格式的数据呢?或者转换成sql文件? 下面的例子是将json格式的数据准换成以#_#分割的文本数据,也可用于生成sql文件. [root@bogon ...