欲练神功,引刀自宫。为了避免内存管理的烦恼,Java咔嚓一下,把指针砍掉了。当年.Net也追随潮流,咔嚓了一下,化名小桂子,登堂入室进了皇宫。康熙往下面一抓:咦?还在?——原来是假太监韦小宝。
打开unsafe选项,C#指针就biu的一下子蹦出来了。指针很强大,没必要抛弃这一强大的工具。诚然,在大多数情况下用不上指针,但在特定的情况下还是需要用到的。比如:
(1)大规模的运算中使用指针来提高性能;
(2)与非托管代码进行交互;
(3)在实时程序中使用指针,自行管理内存和对象的生命周期,以减少GC的负担。
目前使用指针的主要语言是C和C++。但是由于语法限制,C和C++中的指针的玩法很单调,在C#中,可以进行更优雅更好玩的玩法。本文是《重新认识C#: 玩转指针》一文的续篇,主要是对《重新认识C#: 玩转指针》内容进行总结和改进。
C#下使用指针有两大限制:
(1)使用指针只能操作struct,不能操作class;
(2)不能在泛型类型代码中使用未定义类型的指针。
第一个限制没办法突破,因此需要将指针操作的类型设为struct。struct + 指针,恩,就把C#当更好的C来用吧。对于第二个限制,写一个预处理器来解决问题。
下面是我写的简单的C#预处理器的代码,不到200行:
然后编译为 Csmacro.exe ,放入系统路径下。在需要使用预处理器的项目中添加 Pre-build event command lind:
Csmacro.exe $(ProjectDir)
Visual Studio 有个很好用的关键字 “region” ,我们就把它当作我们预处理器的关键字。include 一个文件的语法是:
#region include "xxx.cs"
#endregion
一个文件中可以有多个 #region include 块。
被引用的文件不能全部引用,因为一个C#文件中一般包含有 using,namespace … 等,全部引用的话会报编译错误。因此,在被引用文件中,需要通过关键字来规定被引用的内容:
#region mixin
…
#endregion
这个预处理器比较简单。被引用的文件中只能存在一个 #region mixin 块,且在这个region的内部,不能有其它的region块。
预处理器 Csmacro.exe 的作用就是找到所有 cs 文件中的 #region include 块,根据 #region include 路径找到被引用文件,将该文件中的 #region mixin 块 取出,替换进 #region include 块中,生成一个以_Csmacro.cs结尾的新文件 。
由于C#的两个语法糖“partial” 和 “using”,预处理器非常好用。如果没有这两个语法糖,预处理器会很丑陋不堪。(谁说语法糖没价值!一些小小的语法糖,足以实现新的编程范式。)
partial 关键字 可以保证一个类型的代码存在几个不同的源文件中,这保证了预处理器的执行,您可以像写正常的代码一样编写公共部分代码,并且正常编译。
using 关键字可以为类型指定一个的别名。这是一个不起眼的语法糖,却在本文中非常重要:它可以为不同的类型指定一个相同的类型别名。之所以引入预处理器,就是为了复用包含指针的代码。我们可以将代码抽象成两部分:变化部分和不变部分。一般来说,变化部分是类型的型别,如果还有其它非类型的变化,我们也可以将这些变化封装成新的类型。这样一来,我们可以将变化的类型放在源文件的顶端,使用using 关键字,命名为固定的别名。然后把不变部分的代码,放在 #region mixin 块中。这样的话,让我们需要 #region include 时,只需要在 #region include 块的前面(需要在namespace {} 的外部)为类型别名指定新的类型。
举例说明,位图根据像素的格式可以分为很多种,这里假设有两种图像,一种是像素是一个Byte的灰度图像ImageU8,一个是像素是一个Argb32的彩色图像ImageArgb32。ImageU8代码如下:
在 ImageArgb32 中,我们也要写重复的代码:
对于 Width和Height属性,我们可以建立基类来进行抽象和复用,然而,对于m_pointer和SetValue方法,如果放在基类中,则需要抹去类型信息,且变的十分丑陋。由于C#不支持泛型类型的指针,也无法提取为泛型代码。
使用 Csmacro.exe 预处理器,我们就可以很好的处理。
首先,建立一个模板文件 Image_Template.cs
然后建立一个基类 BaseImage,再从BaseImage派生ImageU8和ImageArgb32。两个派生类都是 partial 类:
下面我们建立一个 ImageU8_ClassHelper.cs 文件,来 #region include 引用上面的模板文件:
2
3 using System;
4 namespace XXX
5 {
6 public partial class ImageU8
7 {
8 #region include "Image_Template.cs"
9 #endregion
10 }
11 }
编译,编译器会自动生成文件 “ImageU8_ClassHelper_Csmacro.cs” 。将这个文件引入项目中,编译通过。这个文件内容是:
对于 ImageArgb32 类也可以进行类似操作。
从这个例子可以看出,使用 partial 关键字,能够让原代码、模板代码、ClassHelper代码三者共存。使用 using 关键字,可以分离出代码中变化的部分出来。
下面是我写的图像操作的一些模板代码:
(1)通过模板提供指针和索引器:
(2)通过模板提供常用的操作和Lambda表达式支持
配合lambda表达式,用起来很爽。在方法“FindTemplate”中,有这一句:
if (pattern >= 0 && srcStart[rr * stride + cc] != pattern)
其中 srcStart[rr * stride + cc] 是 TPixel 不定类型,而 pattern 是 int 类型,两者之间需要进行比较,但是并不是所有的类型都提供和整数之间的 != 操作符。为此,我建立了个新的模板 TPixel_Template。
(3)通过模板提供 != 操作符 的定义
这里,在 #region mixin 块被注释掉了,不注释掉编译器会报错。注释之后,不会影响程序预处理。
通过 ClassHelper类来使用模板:
由于 Argb32 未提供和 int 之间的比较,因此,在这里 #region include "TPixel_Template.cs"。而Byte可以与int比较,因此,在ImageU8中,就不需要#region include "TPixel_Template.cs":
是不是很有意思呢?强大的指针,结合C#强大的语法和快速编译,至少在图像处理这一块是很好用的。
本文转自xiaotie博客园博客,原文链接http://www.cnblogs.com/xiaotie/archive/2010/07/19/1780440.html如需转载请自行联系原作者
xiaotie 集异璧实验室(GEBLAB)