在《C#模板編程(1):有了泛型,為什么還需要模板?》文中,指出了C#泛型的局限性,為了突破這個(gè)局限性,我們需要模板編程。但是,C#語法以及IDE均不支持C#模板編程,怎么辦呢?自己動(dòng)手,豐衣足食,編寫自己的C#預(yù)處理器。
一、C#預(yù)處理機(jī)制設(shè)計(jì)
問題的關(guān)鍵就是在C#的源文件中引入include機(jī)制,設(shè)計(jì)下面的語法:
(1) 引入:#region include <path> #endregion
(2) 被引:#region mixin … #endgion
例子:假設(shè)A.cs需要引用B.cs中的代碼。A文件內(nèi)容為:
#region include "B.cs"
#endregion
XXX
B.cs 文件內(nèi)容為:
#region mixin
MMM
#endregion
ZZZ
運(yùn)行預(yù)處理器,對(duì)A文件進(jìn)行處理,生成第三個(gè)文件A_.cs:
MMM
XXX
二、實(shí)現(xiàn)
編寫預(yù)處理器:Csmacro.exe[Csmacro.zip](意思是CSharp Macro)程序,代碼如下:
2 using System.Collections.Generic;
3 using System.IO;
4 using System.Text;
5 using System.Text.RegularExpressions;
6
7 namespace Orc.Util.Csmacro
8 {
9 class Program
10 {
11 static Regex includeReg = new Regex("#region\\s+include.+\\s+#endregion");
12 static Regex mixinReg = new Regex("(?<=#region\\s+mixin\\s)[\\s|\\S]+(?=#endregion)");
13
14 /// <summary>
15 /// Csmacro [dir|filePath]
16 ///
17 /// 語法:
18 /// #region include ""
19 /// #endregion
20 ///
21 /// </summary>
22 /// <param name="args"></param>
23 static void Main(string[] args)
24 {
25 if (args.Length != 1)
26 {
27 PrintHelp();
28 return;
29 }
30
31 String filePath = args[0];
32
33 Path.GetDirectoryName(filePath);
34 String dirName = Path.GetDirectoryName(filePath);
35 #if DEBUG
36 Console.WriteLine("dir:" + dirName);
37 #endif
38 String fileName = Path.GetFileName(filePath);
39 #if DEBUG
40 Console.WriteLine("file:" + fileName);
41 #endif
42
43 if (String.IsNullOrEmpty(fileName))
44 {
45 Csmacro(new DirectoryInfo(dirName));
46 }
47 else
48 {
49 if (fileName.EndsWith(".cs") == false)
50 {
51 Console.WriteLine("Csmacro只能處理后綴為.cs的源程序.");
52 }
53 else
54 {
55 Csmacro(new FileInfo(fileName));
56 }
57 }
58
59 Console.WriteLine("[Csmacro]:處理完畢.");
60
61 #if DEBUG
62 Console.ReadKey();
63 #endif
64 }
65
66 static void Csmacro(DirectoryInfo di)
67 {
68 Console.WriteLine("[Csmacro]:進(jìn)入目錄" + di.FullName);
69
70 foreach (FileInfo fi in di.GetFiles("*.cs", SearchOption.AllDirectories))
71 {
72 Csmacro(fi);
73 }
74 }
75
76 static void Csmacro(FileInfo fi)
77 {
78 String fullName = fi.FullName;
79 if (fi.Exists == false)
80 {
81 Console.WriteLine("[Csmacro]:文件不存在-" + fullName);
82 }
83 else if (fullName.EndsWith("_Csmacro.cs"))
84 {
85 return;
86 }
87 else
88 {
89 String text = File.ReadAllText(fullName);
90
91 DirectoryInfo parrentDirInfo = fi.Directory;
92
93 MatchCollection mc = includeReg.Matches(text);
94 if (mc == null || mc.Count == 0) return;
95 else
96 {
97 Console.WriteLine("[Csmacro]:處理文件" + fullName);
98
99 StringBuilder sb = new StringBuilder();
100 Match first = mc[0];
101 Match last = mc[mc.Count - 1];
102
103 sb.Append(text.Substring(0, first.Index));
104
105 foreach (Match m in mc)
106 {
107 String tmp = Csmacro(parrentDirInfo, m.Value);
108 sb.Append(tmp);
109 }
110
111 Int32 lastEnd = last.Index + last.Length;
112 if (lastEnd < text.Length)
113 {
114 sb.Append(text.Substring(lastEnd));
115 }
116 String newName = fullName.Substring(0, fullName.Length - 3) + "_Csmacro.cs";
117 if (File.Exists(newName))
118 {
119 Console.WriteLine("[Csmacro]:刪除舊文件" + newName);
120 }
121 File.WriteAllText(newName, sb.ToString());
122 Console.WriteLine("[Csmacro]:生成文件" + newName);
123 }
124 }
125 }
126
127 static String Csmacro(DirectoryInfo currentDirInfo, String text)
128 {
129 String outfilePath = text.Replace("#region", String.Empty).Replace("#endregion", String.Empty).Replace("include",String.Empty).Replace("\"",String.Empty).Trim();
130 try
131 {
132 if (Path.IsPathRooted(outfilePath) == false)
133 {
134 outfilePath = currentDirInfo.FullName + @"\" + outfilePath;
135 }
136 FileInfo fi = new FileInfo(outfilePath);
137 if (fi.Exists == false)
138 {
139 Console.WriteLine("[Csmacro]:文件" + fi.FullName + "不存在.");
140 return text;
141 }
142 else
143 {
144 return GetMixinCode(File.ReadAllText(fi.FullName));
145 }
146 }
147 catch (Exception ex)
148 {
149 Console.WriteLine("[Csmacro]:出現(xiàn)錯(cuò)誤(" + outfilePath + ")-" + ex.Message);
150 }
151 finally
152 {
153 }
154 return text;
155 }
156
157 static String GetMixinCode(String txt)
158 {
159 Match m = mixinReg.Match(txt);
160 if (m.Success == true)
161 {
162 return m.Value;
163 }
164 else return String.Empty;
165 }
166
167 static void PrintHelp()
168 {
169 Console.WriteLine("Csmacro [dir|filePath]");
170 }
171 }
172 }
173
174
編譯之后,放在系統(tǒng)路徑下(或放入任一在系統(tǒng)路徑下的目錄)。然后,在VS的項(xiàng)目屬性的Build Events的Pre-build event command line中寫入“Csmacro.exe $(ProjectDir)”,即可在編譯項(xiàng)目之前,對(duì)$(ProjectDir)目錄下的所有cs程序進(jìn)行預(yù)處理。
Csmacro.exe 對(duì)于包含#region include <path> #endregion代碼的程序xx.cs,預(yù)處理生成名為 xx_Csmacro.cs的文件;對(duì)于文件名以"Csmacro.cs”結(jié)尾的文件,則不進(jìn)行任何處理。
使用時(shí)要注意:
(1)#region include <path> 與 #endregion 之間不能有任何代碼;
(2)#region mixin 與 #endgion 之間不能有其它的region
(3)不支持多級(jí)引用
三、示例
下面,以《C#模板編程(1):有了泛型,為什么還需要模板?》文尾的例子說明怎樣編寫C#模板程序:
(1)建立一個(gè)模板類 FilterHelper_Template.cs ,編譯通過:
2 using TCache = System.Int32;
3 using TKernel = System.Int32;
4
5 using System;
6 using System.Collections.Generic;
7 using System.Text;
8
9 namespace Orc.SmartImage.Hidden
10 {
11 static class FilterHelper_Template
12 {
13 #region mixin
14
15 // 本算法是錯(cuò)誤的,只為說明C#模板程序的使用。
16 public unsafe static UnmanagedImage<TPixel> Filter(this UnmanagedImage<TPixel> src, FilterKernel<TKernel> filter)
17 {
18 TKernel* kernel = stackalloc TKernel[filter.Length];
19
20 Int32 srcWidth = src.Width;
21 Int32 srcHeight = src.Height;
22 Int32 kWidth = filter.Width;
23 Int32 kHeight = filter.Height;
24
25 TPixel* start = (TPixel*)src.StartIntPtr;
26 TPixel* lineStart = start;
27 TPixel* pStart = start;
28 TPixel* pTemStart = pStart;
29 TPixel* pT;
30 TKernel* pK;
31
32 for (int c = 0; c < srcWidth; c++)
33 {
34 for (int r = 0; r < srcHeight; r++)
35 {
36 pTemStart = pStart;
37 pK = kernel;
38
39 Int32 val = 0;
40 for (int kc = 0; kc < kWidth; kc++)
41 {
42 pT = pStart;
43 for (int kr = 0; kr < kHeight; kr++)
44 {
45 val += *pK * *pT;
46 pT++;
47 pK++;
48 }
49
50 pStart += srcWidth;
51 }
52
53 pStart = pTemStart;
54 pStart++;
55 }
56
57 lineStart += srcWidth;
58 pStart = lineStart;
59 }
60 return null;
61 }
62 #endregion
63 }
64 }
65
66
這里,我使用了命名空間Hidden,意思是這個(gè)命名空間不想讓外部使用,因?yàn)樗悄0孱悺?/p>
(2)編寫實(shí)例化模板類 ImageU8FilterHelper.cs
2 using System.Collections.Generic;
3 using System.Text;
4
5 namespace Orc.SmartImage
6 {
7 using TPixel = System.Byte;
8 using TCache = System.Int32;
9 using TKernel = System.Int32;
10
11 public static partial class ImageU8FilterHelper
12 {
13 #region include "FilterHelper_Template.cs"
14 #endregion
15 }
16 }
注意:這里使用 partial class 是為了使代碼與預(yù)處理器生成的代碼共存,不產(chǎn)生編譯錯(cuò)誤。
(3)編譯項(xiàng)目,可以發(fā)現(xiàn),預(yù)處理器自動(dòng)生成了代碼文件ImageU8FilterHelper_Csmacro.cs,且編譯通過:
2 using System.Collections.Generic;
3 using System.Text;
4
5 namespace Orc.SmartImage
6 {
7 using TPixel = System.Byte;
8 using TCache = System.Int32;
9 using TKernel = System.Int32;
10
11 public static partial class ImageU8FilterHelper
12 {
13
14 // 本算法是錯(cuò)誤的,只為說明C#模板程序的使用。
15 public unsafe static UnmanagedImage<TPixel> Filter(this UnmanagedImage<TPixel> src, FilterKernel<TKernel> filter)
16 {
17 TKernel* kernel = stackalloc TKernel[filter.Length];
18
19 Int32 srcWidth = src.Width;
20 Int32 srcHeight = src.Height;
21 Int32 kWidth = filter.Width;
22 Int32 kHeight = filter.Height;
23
24 TPixel* start = (TPixel*)src.StartIntPtr;
25 TPixel* lineStart = start;
26 TPixel* pStart = start;
27 TPixel* pTemStart = pStart;
28 TPixel* pT;
29 TKernel* pK;
30
31 for (int c = 0; c < srcWidth; c++)
32 {
33 for (int r = 0; r < srcHeight; r++)
34 {
35 pTemStart = pStart;
36 pK = kernel;
37
38 Int32 val = 0;
39 for (int kc = 0; kc < kWidth; kc++)
40 {
41 pT = pStart;
42 for (int kr = 0; kr < kHeight; kr++)
43 {
44 val += *pK * *pT;
45 pT++;
46 pK++;
47 }
48
49 pStart += srcWidth;
50 }
51
52 pStart = pTemStart;
53 pStart++;
54 }
55
56 lineStart += srcWidth;
57 pStart = lineStart;
58 }
59 return null;
60 }
61 }
62 }
63
64
四、小結(jié)
這樣一來,C#模板類使用就方便了很多,不必手動(dòng)去處理模板類的復(fù)制和粘帖。雖然仍沒有C++模板使用那么自然,畢竟又近了一步。:P