重新认识C#: 玩转指针

📅 2026/7/6 4:13:05
重新认识C#: 玩转指针
许多文章并不鼓励在C#下使用指针开发不过本文偏偏要这样做。在大量尝试C#下使用指针开发之后你会对C#有更深的认识。在说C#下的指针之前需要提一下C/CLI。C/CLI 我们可以把它看作两部分Native C和 Managed C两者可以无缝结合。对C#我们也可以把它看作两部分Managed C# 和 Unmanaged C#。Managed C# 和 Unmanaged C# 是我杜撰的两个词前者就是我们通常的C#后者就是使用指针、Struct和非托管内存的C#。事实证明Unmanaged C#也可以玩的十分优雅——它具有C语言的大部分特性却比C语言好用的多。 C# 与 C/CLI之间的对应关系见下图C/CLI默认是 Native C而C# 默认是 Managed C# 。除了不能内嵌汇编以及编译方式不同之外C/CLI和C#两者在层面上几乎是等价的。其中C/CLI略微偏底层一点C#略微偏高层一点。尽管略微偏高层一点C#仍然可以当成准系统语言来玩。你可以将Unmanaged C# 当作 mini c 来玩区别只是C 语言一般是编译执行的而 Unmanaged C# 是先编译成 IL 再使用Ngen编译成机器码或在运行时编译成机器码执行。在C#下不鼓励使用指针这是因为C#的定位是应用级的开发如果我们把它定位为低一级别的开发那么就需要大量的使用指针了。大量使用指针进行Unmanaged C#开发“本质”上就是使用 C 语言。只是因为目前 JIT 技术发展年代仍不够久远导致 Unmamaged C# 的性能较 C 语言 略低。下面画张图描述一下当下的C#语言。当下的C#包含了五种编程范式类C、OO、泛型、Lambda、Dynamic。关于 OO、泛型、Lambda、Dynamic已经有很多文章介绍和总结了关于类C这一块却很少有人写文章详细介绍。就像Ajax重新发现了javascript一样我们也应该去重新发现C#中的Unmanaged 成分。回看程序设计语言的发展史。C语言是一直的王者。但是由于抽象能力不足在C的基础上出现了C后来又出现了帮你管家的保姆Java于是在系统层开发使用C/C在应用层开发使用Java成为一种常见的分工方式。有没有一种语言同时具备Java的快速开发优势和C/C的高性能且能直接访问内存这两个优点呢D语言就是奔着这个目标设计的。许多人对D语言报以厚望。可问题是D语言看起来很美但太草根了各方面都不成熟。C#诞生之后人们认为它和Java的定位是类似的我也一直这样认为。同时我还在寻找能够快速开发、自己管理内存、拥有庞大的类库的另一种语言来进行高性能开发及实时开发。我看过D语言看过Haskell语言都不是我想要的转一圈回来发现原来答案已在自己的手中那就是已经用了很多年的C#——C#的Unmanaged部分。开发过一个软实时系统每秒钟有数百万对象生灭是使用C开发的。C开发效率低下我想寻找一个替代品。最先找到的是Java由于GC的存在在Java下开发软实时系统比较困难以至于出现了专门的Java实时规范和实时Java虚拟机。当时接触C#不久想为什么C#下没人研究实时系统现在知道了答案那就是开发实时性应用相对于JavaC#具有非常大的优势——由于Unmanaged 部分的存在不需要专门的C#实时虚拟机。C# 中GC 是无法直接插手非托管内存的如果只有寥寥无几的对象在托管内存中每一次GC时间十分短暂可以忽略不计。这两年开始进行图像处理方面的程序开发。图像处理开发C/C是王道。不过C/C开发效率低下是个大问题同样需要寻找替代品。最开始我使用的是C/CLI使用后发现C/CLI 不好用它继承了C的所有缺点最不能忍受的是狂慢的编译速度。C/CLI的CLI部分虽然可以使用.Net的庞大的类库但是没有C#自然。有没有一种更好的方式平衡开发效率和运行效率有那就是打开unsafe之后的C#优雅的语法、快速的编译、庞大的类库、完美的IDE、想托管内存就托管内存不想托管就不托管——犀利非常的犀利无比的犀利。在《编写高效的C#图像处理程序——我的实验》和《编写高效的C#图像处理程序——我的实验续》两篇文章中我使用指针得到了近似C语言的性能。因此不必担心C#的性能。C#目前包括的五种编程范式类C、OO、泛型、Lambda、Dynamic这五种编程范式几乎可以无缝的结合熟练使用这些编程范式可以把C#下的指针玩的天花乱坠1Class和Struct中可以直接包含指针成员这样我们可以设计一套自己的继承体系当然得在托管内存中。不过可以将性能攸关部分放在非托管内存中然后将它的指针放到Class中遵循Disposable模式来管理避免内存泄漏。2C#下的泛型不支持泛型Class的指针于是我在《C#模板编程(1):有了泛型为什么还需要模板》和《C#模板编程(2): 编写C#预处理器让模板来的再自然一点》这两篇文章中编写了C#的预处理器再结合using关键字和partial关键字实现了对C模板的模拟用以Unmanaged C#代码的强类型复用。这样处理就写出了几个纯C#开发的高性能C#图像处理基本类见博文《发布我的高性能纯C#图像处理基本类顺便也挑战一下极限。:)》。这些基本类可以通过指针访问图像的像素也可以通过索引器来访问像素也可以通过迭代器来访问像素。通过指针访问速度最快但比较麻烦。通过索引器和迭代器访问比较慢但比较方便。不过通过索引器和迭代器来访问像素很容易误用比如说假设图像是A。A[1,2]可以获得图像的第1行(首行为第0行)第2列首列为第0列的像素。假设想更改这个像素的Red值为5这样写是无效的A[1,2].Red 5。因为A[1,2]是一个Struct实例它是坐标为1,2的像素值的“快照”对A[1,2]的修改无法写入到图像像素中去需要这样写才能实现真正的修改Rgb24 item A[1,2];item.Red 5; A[1,2]item。同理通过迭代器访问也无法修改像素具体值。这样处理既不优雅又容易误用。怎么办呢思来想去我决定取消它改用另一种方式提供对图像像素的便捷访问。什么办法呢Lambda表达式可是问题来了C#下的泛型不支持具体的指针类型作为泛型类型好在关上了一扇门C#又打开了另一扇门——delegate 支持指针类型于是使用《C#模板编程(1):有了泛型为什么还需要模板》和《C#模板编程(2): 编写C#预处理器让模板来的再自然一点》这两篇文章中提出的C#模板开发技巧编写代码有ImageClassHelper_Template.cs这样一来就提供了ForEach扩展方法可以通过指针直接访问具体的像素。同时我也顺便实现了Count和Where两个扩展方法。Count和Where两个扩展方法同时提供了指针版本和非指针版本。然后编写类 Rgb24ImageClassHelperRgb24ImageClassHelper.cs编译之后就可以通过Lambda表达式通过指针来访问 UnmanagedImageRgb24 实例中的像素。例子性能测试为例子与性能测试代码