在《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)程序,代码如下:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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 ,编译通过:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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,且编译通过:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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