C#玩转指针(二):预处理器、using、partial关键字与region的妙用

简介:

欲练神功,引刀自宫。为了避免内存管理的烦恼,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 引用上面的模板文件:

复制代码
 1  using  TPixel  =  System.Byte; 
 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)

相关文章
|
1月前
|
存储 编译器 程序员
【C++ 泛型编程 高级篇】 C++ 14 模版元编程 遍历元组 编译期生成整数序列 std::index_sequence和std::make_index_sequence 使用指南(一)
【C++ 泛型编程 高级篇】 C++ 14 模版元编程 遍历元组 编译期生成整数序列 std::index_sequence和std::make_index_sequence 使用指南
48 0
|
1月前
|
编解码 算法 程序员
【C++ 泛型编程 高级篇】 C++ 14 模版元编程 遍历元组 编译期生成整数序列 std::index_sequence和std::make_index_sequence 使用指南(三)
【C++ 泛型编程 高级篇】 C++ 14 模版元编程 遍历元组 编译期生成整数序列 std::index_sequence和std::make_index_sequence 使用指南
27 0
|
1月前
|
C++ 索引
【C++ 泛型编程 高级篇】 C++ 14 模版元编程 遍历元组 编译期生成整数序列 std::index_sequence和std::make_index_sequence 使用指南(二)
【C++ 泛型编程 高级篇】 C++ 14 模版元编程 遍历元组 编译期生成整数序列 std::index_sequence和std::make_index_sequence 使用指南
26 0
|
3月前
|
存储 编译器 C语言
learn_C_deep_10 extern在多文件下的理解、struct 关键字的理解与柔性数组、union 的内存级布局理解、enum 关键字的基本理解、typedef 的理解与分类、关键字总结
learn_C_deep_10 extern在多文件下的理解、struct 关键字的理解与柔性数组、union 的内存级布局理解、enum 关键字的基本理解、typedef 的理解与分类、关键字总结
|
11月前
|
C++ 容器
C++ vector 赋值、删除、排序类之外的其他函数
C++ vector 赋值、删除、排序类之外的其他函数
78 0
|
数据安全/隐私保护
CE修改器入门:查找多级指针
本关是第6关的加强版,CE 6.X 教程中的4级指针比5.X的要简单些。多级指针就像玩解谜游戏一样,谜团不只一个,盒子中还有盒子。这里面是4级指针,游戏中也有比如8级指针,12级指针等等,思路都是一样的。
568 0
CE修改器入门:查找多级指针
|
存储 数据安全/隐私保护
CE修改器入门:代码替换功能
某些游戏重新开始时,数据会存储在与上次不同的地方, 甚至游戏的过程中数据的存储位置也会变动。在这种情况下,你还是可以简单几步搞定它。这次我将尽量阐述如何运用"代码替换"功能,第五关的数值每次启动教程的时候都会存放在内存不同的位置,所以地址列表中的固定地址是不起作用的。
326 0
CE修改器入门:代码替换功能
|
存储 自然语言处理 安全
【C++】C++入门 --- 命名空间 | 输入输出 | 缺省函数 | 函数重载 | 引用 | 内联函数 | auto关键字 | 基于范围的for循环 | 指针空值
命名空间 | 输入输出 | 缺省函数 | 函数重载 | 引用 | 内联函数 | auto关键字 | 基于范围的for循环 | nullptr
135 0
【C++】C++入门 --- 命名空间 | 输入输出 | 缺省函数 | 函数重载 | 引用 | 内联函数 | auto关键字 | 基于范围的for循环 | 指针空值
|
Java 开发者
方法的定义与使用(方法递归调用)|学习笔记
快速学习 方法的定义与使用(方法递归调用)
方法的定义与使用(方法递归调用)|学习笔记
|
Go
Go实战(二)-变量、语句、函数、指针、关键字(上)
Go实战(二)-变量、语句、函数、指针、关键字
93 0
Go实战(二)-变量、语句、函数、指针、关键字(上)