C#模板编程(2): 编写C#预处理器,让模板来的再自然一点
在《C#模板编程(1):有了泛型,为什么还需要模板?》文中,指出了C#泛型的局限性,为了突破这个局限性,我们需要模板编程。但是,C#语法以及IDE均不支持C#模板编程,怎么办呢?自己动手,丰衣足食,编写自己的C#预处理器。
一、C#预处理机制设计
问题的关键就是在C#的源文件中引入include机制,设计下面的语法:
(1) 引入:#region include <path> #endregion
(2) 被引:#region mixin … #endgion
例子:假设A.cs需要引用B.cs中的代码。A文件内容为:
#region include "B.cs"
#endregion
XXX
B.cs 文件内容为:
#region mixin
MMM
#endregion
ZZZ
运行预处理器,对A文件进行处理,生成第三个文件A_.cs:
MMM
XXX
二、实现
编写预处理器: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]:进入目录" + 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]:出现错误(" + 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
编译之后,放在系统路径下(或放入任一在系统路径下的目录)。然后,在VS的项目属性的Build Events的Pre-build event command line中写入“Csmacro.exe $(ProjectDir)”,即可在编译项目之前,对$(ProjectDir)目录下的所有cs程序进行预处理。
Csmacro.exe 对于包含#region include <path> #endregion代码的程序xx.cs,预处理生成名为 xx_Csmacro.cs的文件;对于文件名以"Csmacro.cs”结尾的文件,则不进行任何处理。
使用时要注意:
(1)#region include <path> 与 #endregion 之间不能有任何代码;
(2)#region mixin 与 #endgion 之间不能有其它的region
(3)不支持多级引用
三、示例
下面,以《C#模板编程(1):有了泛型,为什么还需要模板?》文尾的例子说明怎样编写C#模板程序:
(1)建立一个模板类 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 // 本算法是错误的,只为说明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,意思是这个命名空间不想让外部使用,因为它是模板类。
(2)编写实例化模板类 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 是为了使代码与预处理器生成的代码共存,不产生编译错误。
(3)编译项目,可以发现,预处理器自动生成了代码文件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 // 本算法是错误的,只为说明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
四、小结
这样一来,C#模板类使用就方便了很多,不必手动去处理模板类的复制和粘帖。虽然仍没有C++模板使用那么自然,毕竟又近了一步。:P