博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
c# 扩展方法奇思妙用高级篇四:对扩展进行分组管理
阅读量:6247 次
发布时间:2019-06-22

本文共 8410 字,大约阅读时间需要 28 分钟。

从系列文章开篇到现在,已经实现的很多扩展了,但过多的扩展会给我们带来很多麻烦,试看下图:

 

 面对这么多“泛滥”的扩展,很多人都会感到很别扭,的确有种“喧宾夺主”的感觉,想从中找出真正想用的方法来太难了!尽管经过扩展后的string类很“强大”,但易用性确很差。

 很多人因此感觉扩展应适可而止,不该再继续下去...其实这是一种逃避问题的态度,出现问题我们应该主动去解决,而不是去回避!

 有很多种方法可以解决以上问题,最简单的就是使用将扩展放入不同namespace中,使用时按需using相应namespace,可达到一定效果。但这种方法有很大缺点: 一个命名空间中的扩展若太多同样会让我们的智能提示充斥着扩展方法,扩展太少每次使用都要using多个命名空间,很麻烦。

 先介绍一种简单的方式,先看效果:

 

 图1中前三个以As开始的三个扩展就是采用分组技术后的三类扩展,分别是中文处理、转换操作、正则操作,后面三个图分别对就这三类扩展的具体应用。图2中的有三个中文处理的扩展ToDBC、ToSBC、GetChineseSpell分别是转为半角、转为全角、获取拼音首字母。

 通过这样分组后,string类的智能提示中扩展泛滥的现象得到了解决,使用AsXXX,是以字母A开始,会出现在提示的最前面,与原生方法区分开来。

 采用这种方式有几个缺点:

 1.使用一个扩展要先As一次,再使用具体扩展,比之前多了一步操作:这是分组管理必然的,建议使用频率非常高的还是直接扩展给string类,不要分组。只对使用频率不高的进行分组。

 2.扩展后的智能提示不友好,扩展的方法与Equals、ToString混在了一起,而且没有扩展方法的标志。

 先给出这种方法的实现参考代码,再来改进:

 1 
    
public
 
static
 
class
 StringExtension
 2 
    {
 3 
        
public
 
static
 ChineseString AsChineseString(
this
 
string
 s) { 
return
 
new
 ChineseString(s); }
 4 
        
public
 
static
 ConvertableString AsConvertableString(
this
 
string
 s) { 
return
 
new
 ConvertableString(s); }
 5 
        
public
 
static
 RegexableString AsRegexableString(
this
 
string
 s) { 
return
 
new
 RegexableString(s); }
 6 
    }
 7 
    
public
 
class
 ChineseString
 8 
    {
 9 
        
private
 
string
 s;
10 
        
public
 ChineseString(
string
 s) { 
this
.s 
=
 s; }
11 
        
//
转全角
12 
        
public
 
string
 ToSBC(
string
 input) { 
throw
 
new
 NotImplementedException(); } 
13 
        
//
转半角
14 
        
public
 
string
 ToDBC(
string
 input) { 
throw
 
new
 NotImplementedException(); }
15 
        
//
获取汉字拼音首字母
16 
        
public
 
string
 GetChineseSpell(
string
 input) { 
throw
 
new
 NotImplementedException(); }
17 
    }
18 
    
public
 
class
 ConvertableString
19 
    {
20 
        
private
 
string
 s;
21 
        
public
 ConvertableString(
string
 s) { 
this
.s 
=
 s; }
22 
        
public
 
bool
 IsInt(
string
 s) { 
throw
 
new
 NotImplementedException(); }
23 
        
public
 
bool
 IsDateTime(
string
 s) { 
throw
 
new
 NotImplementedException(); }
24 
        
public
 
int
 ToInt(
string
 s) { 
throw
 
new
 NotImplementedException(); }
25 
        
public
 DateTime ToDateTime(
string
 s) { 
throw
 
new
 NotImplementedException(); } 
26 
    }
27 
    
public
 
class
 RegexableString
28 
    {
29 
        
private
 
string
 s;
30 
        
public
 RegexableString(
string
 s) { 
this
.s 
=
 s; }
31 
        
public
 
bool
 IsMatch(
string
 s, 
string
 pattern) { 
throw
 
new
 NotImplementedException(); }
32 
        
public
 
string
 Match(
string
 s, 
string
 pattern) { 
throw
 
new
 NotImplementedException(); }
33 
        
public
 
string
 Relplace(
string
 s, 
string
 pattern, MatchEvaluator evaluator) { 
throw
 
new
 NotImplementedException(); }
34 
    }

 代码仅是为了说明怎么分组,没有实现,具体实现请参见本系列前面的文章。为了节省空间,很多代码都写成了一行。

 前面提到的第二条缺点,我们改进后,方式二的显示效果如下:

 

 Equals、GetHashCode、ToString 实在去不了,哪位朋友有好办法分享一下吧!不过这次把扩展方法的标志加上。实现比方式一麻烦一下:

 1 
    
public
 
class
 ChineseString
 2 
    {
 3 
        
private
 
string
 s;
 4 
        
public
 ChineseString(
string
 s) { 
this
.s 
=
 s; }
 5 
        
public
 
string
 GetValue() { 
return
 s; }
 6 
    }
 7 
 8 
    
public
 
static
 
class
 CheseStringExtension
 9 
    {
10 
        
public
 
static
 ChineseString AsChineseString(
this
 
string
 s) { 
return
 
new
 ChineseString(s); }
11 
12 
        
public
 
static
 
string
 ToSBC(
this
 ChineseString cs) 
13 
        {
14 
            
string
 s 
=
 cs.GetValue();
//
从ChineseString取出原string
15 
            
char
[] c 
=
 s.ToCharArray();
16 
            
for
 (
int
 i 
=
 
0
; i 
<
 c.Length; i
++
)
17 
            {
18 
                
if
 (c[i] 
==
 
32
) { c[i] 
=
 (
char
)
12288
continue
; }                
19 
                
if
 (c[i] 
<
 
127
) c[i] 
=
 (
char
)(c[i] 
+
 
65248
);
20 
            }
21 
            
return
 
new
 
string
(c);
22 
        }
23 
        
public
 
static
 
string
 ToDBC(
this
 ChineseString cs) { 
throw
 
new
 NotImplementedException(); }
24 
        
public
 
static
 
string
 GetChineseSpell(
this
 ChineseString cs) { 
throw
 
new
 NotImplementedException(); }
25 
    }

 这里需要两个类,一个类ChineseString作为AsXXX的返回值,第二个类ChineseStringExtension是对ChineseString进行扩展的类。能过这种方式,才能显示出扩展的标识符号!每组扩展要两个类,比较麻烦。

 方式一、方式二感觉都不太好,而且扩展组多了,还会有新的问题出现,如下:

 

 也是很要命的!再来看第三种方式,这是我和在讨论单一职责原则时想出来的,先看效果:

 

 

 方法三将所有的扩展精简为一个As<T>!是的,我们仅需要As<T>这一个扩展!T为一接口,通过输入不同的T,展示相应的扩展。这样又解决了扩展组的泛滥问题,先看下实现一个新的扩展组需要写什么代码,先看左图的代码:

 1 
    
public
 
interface
 IConvertableString : IExtension
<
string
>
 { }
 2 
 3 
    
public
 
static
 
class
 ConvertableString
 4 
    {
 5 
        
public
 
static
 
bool
 IsInt(
this
 IConvertableString s)
 6 
        {
 7 
            
int
 i; 
return
 
int
.TryParse(s.GetValue(), 
out
 i);
 8 
        }
 9 
        
public
 
static
 
bool
 IsDateTime(
this
 IConvertableString s)
10 
        {
11 
            DateTime d; 
return
 DateTime.TryParse(s.GetValue(), 
out
 d);
12 
        }
13 
14 
        
public
 
static
 
int
 ToInt(
this
 IConvertableString s)
15 
        {
16 
            
return
 
int
.Parse(s.GetValue());
17 
        }
18 
19 
        
public
 
static
 DateTime ToDateTime(
this
 IConvertableString s)
20 
        {
21 
            
return
 DateTime.Parse(s.GetValue());
22 
        }
23 
    }

 首先定义一个接口IConvertableString,它继承泛型接口IExtension<T>(我定义的一个接口,稍后给出),因为是对string类作扩展,所以泛型参数为string。IConvertableString只需要一个空架子。然后再编写一个扩展类,所有的方法扩展在IConvertableString接口上。

 再来看右图IRegexableString的代码: 

1 
    
public
 
static
 
class
 RegexableString
2 
    {
3 
        
public
 
static
 
bool
 IsMatch(
this
 IRegexableString s, 
string
 pattern)
4 
        { 
throw
 
new
 NotImplementedException(); }
5 
        
public
 
static
 
string
 Match(
this
 IRegexableString s, 
string
 pattern)
6 
        { 
throw
 
new
 NotImplementedException(); }
7 
        
public
 
static
 
string
 Relplace(
this
 IRegexableString s, 
string
 pattern, MatchEvaluator evaluator)
8 
        { 
throw
 
new
 NotImplementedException(); }
9 
    }

 与上一个一样,也是先定义一个空接口,再定义一个扩展类,将方法扩展在空接口上。

 有一点注意一下,扩展的实现中都要使用GetValue获取原始字符串的值。

 最后给出IExtension<T>接口及As<T>扩展的实现:  

 1    public interface IExtension<V>
 2    {
 3        V GetValue();
 4    }
 5
 6    public static class ExtensionGroup
 7    {
 8        private static Dictionary<Type, Type> cache = new Dictionary<Type, Type>();
 9
10        public static T As<T>(this string v) where T : IExtension<string>
11        {
12            return As<T, string>(v);
13        }
14
15        public static T As<T, V>(this V v) where T : IExtension<V>
16        {
17            Type t;
18            Type valueType = typeof(V);
19            if (cache.ContainsKey(valueType))
20            {
21                t = cache[valueType];
22            }
23            else
24            {
25                t = CreateType<T, V>();
26                cache.Add(valueType, t);
27            }
28            object result = Activator.CreateInstance(t, v);
29            return (T)result;
30        }
31        // 通过反射发出动态实现接口T
32        private static Type CreateType<T, V>() where T : IExtension<V>
33        {
34            Type targetInterfaceType = typeof(T);
35            string generatedClassName = targetInterfaceType.Name.Remove(01);
36            //
37            AssemblyName aName = new AssemblyName("ExtensionDynamicAssembly");
38            AssemblyBuilder ab =
39                AppDomain.CurrentDomain.DefineDynamicAssembly(aName, AssemblyBuilderAccess.Run);
40            ModuleBuilder mb = ab.DefineDynamicModule(aName.Name);
41            TypeBuilder tb = mb.DefineType(generatedClassName, TypeAttributes.Public);
42            //实现接口
43            tb.AddInterfaceImplementation(typeof(T));
44            //value字段
45            FieldBuilder valueFiled = tb.DefineField("value"typeof(V), FieldAttributes.Private);
46            //构造函数
47            ConstructorBuilder ctor = tb.DefineConstructor(MethodAttributes.Public,
48                CallingConventions.Standard, new Type[] typeof(V) });
49            ILGenerator ctor1IL = ctor.GetILGenerator();
50            ctor1IL.Emit(OpCodes.Ldarg_0);
51            ctor1IL.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes));
52            ctor1IL.Emit(OpCodes.Ldarg_0);
53            ctor1IL.Emit(OpCodes.Ldarg_1);
54            ctor1IL.Emit(OpCodes.Stfld, valueFiled);
55            ctor1IL.Emit(OpCodes.Ret);
56            //GetValue方法
57            MethodBuilder getValueMethod = tb.DefineMethod("GetValue",
58                MethodAttributes.Public | MethodAttributes.Virtual, typeof(V), Type.EmptyTypes);
59            ILGenerator numberGetIL = getValueMethod.GetILGenerator();
60            numberGetIL.Emit(OpCodes.Ldarg_0);
61            numberGetIL.Emit(OpCodes.Ldfld, valueFiled);
62            numberGetIL.Emit(OpCodes.Ret);
63            //接口实现
64            MethodInfo getValueInfo = targetInterfaceType.GetInterfaces()[0].GetMethod("GetValue");
65            tb.DefineMethodOverride(getValueMethod, getValueInfo);
66            //
67            Type t = tb.CreateType();
68            return t;
69        }
70    }

 代码比较长,先折叠起来,逐层打开分析吧!

 IExtension<V>只定义一个方法GetValue,用于将As<T>后将原始的值取出。

 ExtensionGroup定义了As<T>扩展,我们先看下值的传递过程。调用语句:"123".As<IConvertableString>().ToInt();

 首先,"123" 是个字符串,As<IConvertableString>后转换成了IConvertableString接口的实例,ToInt时使用GetValue将"123"从IConvertableString接口的实例中取出进行处理。

 关键在“IConvertableString接口的实例”,前面我们并没有具体实现IConvertableString接口的类,怎么出来的实例呢?我们这里用反射发出动态生成了一个实现IConvertableString接口的类。具体是由ExtensionGroup中的私有函数CreateType<T, V>完成的,在这里T传入的是IConvertableString,V传入的是string,返回的值就是实现了IConvertableString接口的一个类的Type.由CreateType<T, V>动态实现的类“模样”如下:

 1 
    
class
 ConvertableString : IConvertableString
 2 
    {
 3 
        
private
 
string
 value;
 4 
        
public
 ConvertableString(
string
 value)
 5 
        {
 6 
                
this
.value 
=
 value;
 7 
        }
 8 
        
public
 
string
 GetValue()
 9 
        {
10 
            
return
 value;
11 
        }
12 
    }

 如果此处不用反射发出动态生成这么一个,那么我们就要手工写一个,每个扩展组都要相应的写一个,很麻烦的。

 为了提高性能,对反射发出的类型进行了缓存,保存在cache成员中。

 方式三有点复杂,主要是因为我们是给sealed类进行扩展,无法从它们继承。

 最后给出测试代码: 

1 
    
public
 
static
 
void
 Test()
2 
    {
3 
        
int
 i 
=
 
"
123
"
.As
<
IConvertableString
>
().ToInt();
4 
        DateTime d 
=
 
"
2009年8月29日
"
.As
<
IConvertableString
>
().ToDateTime();
5 
    }

  

 三种方式,我最喜欢第三种,它仅需要一个As<T>,而且是对接口进行扩展,感觉更OO一些。

 三种方式都不完美,我会努力改进,大家多提些建议啊。

转载于:https://www.cnblogs.com/ywsoftware/archive/2013/06/09/3128770.html

你可能感兴趣的文章
CC430F6137 芯片上集成的外设寄存器地址<-->cc430f6137.cmd
查看>>
Ubuntu14.04 LTS下安装Composer
查看>>
tomcat session会话复制
查看>>
Spring 获取当前web的根路径
查看>>
根据EventID邮件通知并发送详细日志信息
查看>>
mybatis deal with empty result list. 查询结果为empty。
查看>>
Oracle中null的使用详解
查看>>
HTML --URL及URL字符编码
查看>>
java --关注/取消关注
查看>>
2016学习Linux的决心书(老男孩教育)
查看>>
系分----第一章(计算机组成与体系结构)
查看>>
python中子类调用父类的初始化方法
查看>>
C语言新人常见问题
查看>>
<笔试><面试>编写一个排序函数,实现,既可以排序整形数组,又可以排序字符串。...
查看>>
转发的博文,关于webApp
查看>>
回调打印菱形
查看>>
linux下根目录注释,文件类型及应用程序组成部分
查看>>
华为 VBST 异常收敛
查看>>
testlink+vertrigoserv搭载测试管理系统
查看>>
6. ZigZag Conversion
查看>>