Enum 流式代码自动生成(二进制)
使用 LINQ 表达式树为枚举自动生成二进制流式方法。
目录
引言
您是否显式处理二进制流(使用 BinaryReader\Writer
)?您是否还在这些流中处理枚举数据?那么您很可能想阅读本文。我将介绍我为了自动生成代码以将枚举值流式传输到/从二进制流而必须解决的一个问题(它隐藏了类型转换等)。我提出的解决方案允许我指定枚举在流中的大小,即使该大小与枚举的实现不同。例如,System.TypeCode
枚举使用 int
作为其底层类型来实现。但是,使用此系统,我可以告诉代码生成器将流式传输到/从源的值视为 byte
而不是 int
。
背景
不久前,我将一些代码从 C++/CLR 项目移植到了 C#。该项目处理对象代码和 PE 相关文件,因此最初在 C++/CLR 中实现部分代码更有益(因为我可以直接访问代码定义,例如 IMAGE_SYMBOL
)。原始 C++ 代码的一部分是我编写的一个用于流式传输枚举值的模板化函数。
template<typename TEnum>
TEnum /*EndianReader_*/StreamEnumUInt8(KSoft::IO::EndianReader^ s)
{
return cli::safe_cast<TEnum>( s->ReadByte() );
}
template<typename TEnum>
void /*EndianWriter_*/StreamEnumUInt8(KSoft::IO::EndianWriter^ s, TEnum value)
{
s->Write(cli::safe_cast<byte>( value ));
}
依此类推,适用于各种整数类型。注意:EndianReader\Writer
是 BinaryReader\Writer
的专用实现,它支持以不同于宿主系统字节序的主机系统字节序的格式读取流。
众所周知,C# 不支持模板(我属于不喜欢这一事实的阵营,抱歉),因此仅凭一些更改此解决方案将无法移植。在我最近在 CodeProject 上完成了 我的第一篇文章,其中介绍了为访问私有成员自动生成代码,我重新戴上了 Linq.Expressions 帽子,并开始思考如何使用表达式树解决这个问题。
起初,我只想拥有一个泛型类,我将枚举类型作为泛型参数提供,并让它根据枚举的底层类型自动确定用于流式传输到/从源的整数类型。然而,我认为这不仅比我想象的更麻烦,而且还意味着泛型类将完全依赖于枚举的类型定义。这对于将来的更改,或者对于您没有源代码的代码,或者在枚举在不同位置以不同大小存储的奇怪情况下,并不那么友好。
因此,我提出了泛型类 EnumBinaryStreamer<TEnum, TStreamType>
。TEnum
显然是枚举类型的参数,而 TStreamType
是将用于将 TEnum
值流式传输到/从二进制源的基于整数的类型(例如,uint
\System.UInt32
)。
使用代码
有两种方法可以使用该系统:通过完全限定类型名称(或别名)或通过接口实例。EnumBinaryStreamer
类/实例公开三个公共方法:Read
(两个重载)和 Write
,这些是任何人真正关心的(除非您对内部细节感到好奇)。
我以如下方式创建并命名一个别名:
using TypeCodeStreamer8 = EnumBinaryStreamer<System.TypeCode, byte>;
using TypeCodeStreamer32 = EnumBinaryStreamer<System.TypeCode, int>;
using TypeCodeStreamer64 = EnumBinaryStreamer<System.TypeCode, long>;
您可以使用接口实例来使用该系统:
var streamer_instance = EnumBinaryStreamer<System.TypeCode, int>.Instance;
// or
var streamer_instance = TypeCodeStreamer32.Instance;
// NOTE: streamer_instance's type is IEnumBinaryStreamer<TEnum, TStreamType>
好的,我们已经有了一种访问系统接口的方法。现在我们该如何使用它?以下内容取自 EnumStreamerTest.cs(请参阅文章的源文件):
using (var ms = new System.IO.MemoryStream())
using (var br = new BinaryReader(ms))
using (var bw = new BinaryWriter(ms))
{
const System.TypeCode kExpectedValue = System.TypeCode.String;
var value = kExpectedValue;
// Using an alias to write an enum value to the BinaryWriter object 'bw'
TypeCodeStreamer32.Write(bw, value); // -- Usage Example --
ms.Position = 0;
// Using an alias to read an enum value from the BinaryReader object 'br'
TypeCodeStreamer32.Read(br, out value); // -- Usage Example --
Assert.IsTrue(value == kExpectedValue);
//////////////////////////////////////////////////////////////////////////
// Test the instance interface
var streamer_instance = TypeCodeStreamer32.Instance;
ms.Position = 0;
// Using an interface instance to write an enum value to the BinaryWriter object 'bw'
streamer_instance.Write(bw, value); // -- Usage Example --
ms.Position = 0;
// Using an interface instance to read an enum value from the BinaryReader object 'br'
streamer_instance.Read(br, out value); // -- Usage Example --
Assert.IsTrue(value == kExpectedValue);
}
您只需要一个 BinaryReader\Writer
对象和一个枚举值,就可以基本设置使用该系统了!所有细节都由系统的代码进行检查和处理。注意:我使用 CodeContracts 进行健全性检查。如果您无法在代码中使用 CodeContracts(Mono.NET?),那么您将面临一些额外的工作(您需要重写参数验证)。
注意:内部(在 EnumBinaryStreamerBase
的 InitializeMethodDictionaries
方法中)我引用了 Omer Mor 的 EnumComparer
类,但它对于代码的正常工作不是必需的。您只需要在 InitializeMethodDictionaries
方法中使用它的地方,将传递给 Dictionary
构造函数的 EnumComparer.Instance
参数删除即可。
代码背后
该系统由四个类(EnumUtils
、EnumBinaryStreamerBase
、EnumBinaryStreamerBase.StreamType
、EnumBinaryStreamer
)和一个接口(IEnumBinaryStreamer
)组成。
EnumUtils
包含一些与枚举类型的底层实现以及各种支持的底层整数类型相关的实用工具。这里的代码及其注释应该不言自明。
EnumBinaryStreamerBase
及其内部的 StreamType
类负责构建用于流式传输整数数据的各种 BinaryReader\Writer
方法的 MethodInfo
引用。MethodInfo
在代码生成过程中是必需的。我将其定义为一个独立于 EnumBinaryStreamer<TEnum, TStreamType>
的类,这样就不会为 EnumBinaryStreamer
的每个实现都存在静态 MethodInfo
字典等的实例。由于它是一个泛型类,每个 TEnum
都会导致新的实现被分配和初始化,因此最终只会重复处理和占用内存。
EnumBinaryStreamer<TEnum, TStreamType>
是执行所有健全性检查和代码生成的地方。有关验证检查类型参数的信息,请参见 cctor(静态构造函数)。有关代码生成逻辑和文档,请参见 GenerateReadMethod
和 GenerateWriteMethod
。EnumBinaryStreamer
显式实现了 IEnumBinaryStreamer
接口,以便您可以使用实例来访问 Read\Write 方法(以防类型名称或别名变得太长,影响代码美观)。
结论/结束语
我意识到在这篇文章中我没有过多深入介绍后端的 Linq.Expressions 代码。这是故意的。我相信通过阅读代码和支持性注释可以理解代码生成背后的逻辑。我在上一篇文章中 尝试过深入解释 Linq.Expression 的细节。在撰写这篇文章时,我真的不想重复那些我认为简单的 Expressions 代码。
这是否让我成为一个糟糕的文章作者?我希望不是。一个懒惰的?很可能。我意识到,对于对 .NET 反射了解很少的人来说,可能会在理解这段代码的内部机制时遇到困难。然而,这就是为什么我将本文标记为“中级”的原因。至于 Linq.Expression 代码,您真正需要的是 参考 MSDN。有了所有这些以及我的注释,理解实现代码应该不是问题。但是,如果有人遇到问题,请随时留言,我会尽力给出您一个满意的答复 :p!
参考文献
- 生成私有成员访问器
- 通过泛型 EnumComparer 加速基于枚举的字典 - 作者:Omer Mor
- CodeContracts - Microsoft
- System.Linq.Expressions - MSDN
历史
- 2011 年 11 月 1 日 - 首次创建文章。