c#擴展方法奇思妙用高級篇四:對擴展進行分組管理

 從系列文章開篇到現在,已經實現的很多擴展了,但過多的擴展會給我們帶來很多麻煩,試看下圖:

 

 面對這麼多“氾濫”的擴展,很多人都會感到很彆扭,的確有種“喧賓奪主”的感覺,想從中找出真正想用的方法來太難了!儘管經過擴展後的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)12288continue; }                
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一些。

 三種方式都不完美,我會努力改進,大家多提些建議啊。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章