在《C#模板编程(1):有了泛型,为什么还需要模板?》文中,指出了C#泛型的局限性,为了突破这个局限性,我们需要模板编程。但是,C#语法以及IDE均不支持C#模板编程,怎么办呢?自己动手,丰衣足食,编写自己的C#预处理器。

一、C#预处理机制设计

问题的关键就是在C#的源文件中引入include机制,设计下面的语法:

(1) 引入:#region include <path> #endregion

(2) 被引:#region mixin … #endgion

 

例子:假设A.cs需要引用B.cs中的代码。A文件内容为:

XXX

#region include "B.cs"
#endregion

XXX

B.cs 文件内容为:

YYY

#region mixin

MMM

#endregion

ZZZ

运行预处理器,对A文件进行处理,生成第三个文件A_.cs:

XXX

MMM

XXX

二、实现

编写预处理器:Csmacro.exe[Csmacro.zip](意思是CSharp Macro)程序,代码如下:

Csmacro
  1 using System;
  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 == 0return;
 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 ,编译通过:

FilterHelper_Template.cs
 1 using TPixel = System.Byte; 
 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

ImageU8FilterHelper.cs
 1 using System; 
 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,且编译通过:

ImageU8FilterHelper_Csmacro.cs
 1 using System; 
 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