• C# 方法调用的切换器 Update 2015.02.02


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

    通常的做法,最简单的就是用很多 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

  • 相关阅读:
    Java实现HadoopHA集群的hdfs控制
    Hadoop-2.8.5的HA集群搭建
    Python实现bing必应壁纸下载
    使用Python3将Markdown(.md)文本转换成 html、pdf
    使用GitHub作Free图床
    JavaMail实践--实现邮件发送
    Python3实现图片转字符画
    Java编写的Java编辑器_JavaIDE
    Java实现简易版计算器
    Java实现Excel表格操作--API:jxl
  • 原文地址:https://www.cnblogs.com/cyjb/p/MethodSwitcher.html
Copyright © 2020-2023  润新知