您的位置:首页 > 编程语言 > C语言/C++

如何在C#中模拟C++的联合(Union)?[C#, C++]

2009-01-07 22:06 204 查看
0 如何阅读本文?

如果你...

...希望了解联合的概念,请阅读“什么是联合?”。

...希望了解联合的内存使用情况,请阅读“联合的内存布局与内存使用情况。”。

...希望了解如何在C#中模拟联合,请阅读“第一次尝试:在C#中模拟这种布局方式。”。

...希望了解在C++中使用联合有哪些要注意的地方,请阅读“在实际的C++代码中,我们是如何使用联合的?”。

...希望了解如何在C#中更好的使用模拟的联合,请阅读“第二次尝试:改进型的联合模拟。”。

...希望了解在C#中使用模拟的联合有些什么注意事项,请阅读“别在模拟的联合中同时使用值类型和引用类型!”。

...希望了解为何我要写这篇文章,请阅读“为什么要在C#里面模拟这个用处不大的东西?”。

否则...

...你应该从头到尾阅读全文。

1 什么是联合?

联合(Union)是一种特殊的类,一个联合中的数据成员在内存中的存储是互相重叠的。每个数据成员都在相同的内存地址开始。分配给联合的存储区数量是“要包含它最大的数据成员”所需的内存数。同一时刻只有一个成员可以被赋给一个值。

下面我们来看看C++中如何表达联合:

// Code #01

union TokenValue

// Code #02

int _tmain(int argc, _TCHAR* argv[])

// Code #03

int _tmain(int argc, _TCHAR* argv[])

// Code #04

[StructLayout(LayoutKind.Explicit, Size=8)]

struct TokenValue

// Code #05

union TokenValue

int _tmain(int argc, _TCHAR* argv[])

// Code #06

class Program

public struct Token

// Code #07

System.DayOfWeek d = 0;

Console.WriteLine(d);

该代码能正确输出Sunday——一个星期的第一天(西方习惯),也是该枚举的第一个成员。

一般情况下,0对应着枚举的第一个成员(除非你在定义枚举的时候,把第一个成员指定为别的值,并为别的成员赋予0值)。这样,我们就不难看出代码的输出是合理的,而且代码本身也是安全的。

6 别在模拟的联合中同时使用值类型和引用类型!

到目前为止,我们所模拟的联合中,所有的成员都是值类型,如果我们为它加入一个引用类型,例如String呢?

// Code #08

[StructLayout(LayoutKind.Explicit, Size=8)]

struct TokenValue

{

[FieldOffset(0)]

public char _cval;

[FieldOffset(0)]

public int _ival;

[FieldOffset(0)]

public double _dval;

[FieldOffset(0)]

public string _sval;

}

这样,Code #06的代码运行时就会提示出错:

Could not load type 'TokenValue' from assembly 'UnionLab, Version=1.0.1820.28531, Culture=neutral, PublicKeyToken=null' because it contains an object field at offset 0 that is incorrectly aligned or overlapped by a non-object field.

TokenValue初始化的时候,_cval、_ival和_dval都能正确的被赋予对应的零值,而这些零值也能被统一起来(别的值就不行了)。但_sval不同,它是引用类型,如果没有显示初始化为某个有意义的值,它将被赋予null值!这个null值跟之前的有意义的零值是不能被统一起来的!所以,要么你就去掉这个_sval,要么就重新定义它的起始位置(当然,你也得去掉Size=8!),但这样一来,TokenValue就不再称得上联合的模拟了。

在C++中,我们可以直接使用指针来解决这个问题,如Code #05,但C#中,问题就会变得有点辣手。如果你有兴趣的话,可以使用不安全代码(Unsafe code)来试着解决,但这样一来,你的代码又会引入一些新的问题。

7 为什么要在C#里面模拟这个用处不大的东西?[NEW]

相信很多人都有这样一个疑问:为什么要在C#里面模拟这个用处不大的东西?就我个人来说,我始终坚信事物的存在必定有它的理由,否则就不会存在。其实,联合在我们平时的编码中的确很少用到,但在某些情况下,我们必须使用它!.NET为我们提供巨大的便利的同时,也不忘让我们能够与非托管代码交互。你知道,早期的Win32 API使用C来完成的,这里面就有很多函数的参数是以联合的形式表达的,要在C#中跟这些API交互,我们就得“尊重”原函数的用法约束。

8 终点与起点的交界处。

回顾整个探索旅程,我们为了使用联合节省空间的优势,开始了这个模拟的探索,然而,为了弥补联合的不足,我们对这个模拟进行了一番包装,增加了不少额外的代码,直到后来,又发现了在这个模拟中同时使用值类型的成员和引用类型的成员所引发的问题,我们一直都没有停止过探索和思考。正如马斯洛的需要层次理论所描述的,人只要低层次的需要被满足,马上就会转向更高的需要层次,一级一级的,直到攀上最高峰为止。

关于在C#中模拟C++的联合这个话题,我并没有在本文中给予你一个完整的展示,相反,我为你展示的仅仅是一个探索的起点,希望为你带来一丝灵感,让你根据自己的实际情况来定制你的探索旅程。Have a good trip!

参考资料:

Stanley B.Lippman,Josee Lajoie;《C++ Primer中文版(第三版)》;潘爱民,张丽译;中国电力出版社 2002

Microsoft .NET Framework SDK Documentation,Microsoft Corp. 2004

Allen Lee;《关于枚举的种种 [C#, IL, BCL] 》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: