C#中的C++联合

85

我正在将一份用C ++编写的库翻译成C#,其中关键字“union”出现了一次。在一个结构体中。

将其正确地翻译成C#的方式是什么?它是做什么的?看起来像这样;

struct Foo {
    float bar;

    union {
        int killroy;
        float fubar;
    } as;
}

当你与提供这些数据结构的C库进行交互时,使用C#做出这个决定会破坏你的C/C++结构的基本封装,虽然可能更安全。我正在尝试处理类似于这样的东西:struct LibrarySType { AnotherType *anotherTypeBuff; int oneSetOfFlags; int anotherSetOfFlags; union { struct { int structMember1; ... } oneUseOfThisLibraryType; struct { char *structMember2; ... } anotherUseOfThisLibraryType; ... } u; int64 *moreStuff; ... you get the idea } 现在,我并没有发明这种巧妙的数据结构,但它是我需要的供应商API的一部分。 - Steve Wart
7个回答

97

您可以使用显式字段布局来实现:

[StructLayout(LayoutKind.Explicit)] 
public struct SampleUnion
{
    [FieldOffset(0)] public float bar;
    [FieldOffset(4)] public int killroy;
    [FieldOffset(4)] public float fubar;
}

未经测试。这个想法是你的结构体中有两个变量处于相同的位置。当然,你只能使用其中一个。

有关联合体的更多信息,请参阅结构体教程


10
请注意,这仅适用于涉及原始类型(如int和float)的情况。一旦涉及对象,它就不会让你这样做。 - Khoth
13
它能够处理值类型(例如枚举)还是只能处理基元类型? - Taylor Leese
1
如果您有其他复杂类型,第二个可能是一个属性,用于转换到/从另一个类型。这取决于 Union 的使用方式以及您正在移植的其他代码中如何使用它。 - AaronLS
2
“你当然只能使用其中之一”可能不太清楚。你可以访问和设置它们两个,但它们会相互覆盖。我建议澄清这一点。 - Xonatron
@Khoth 你可以在结构体的字段中存储任何东西。显式结构布局也是如此。 - user11462042
显示剩余3条评论

22

如果不知道联合体是如何使用的,就很难决定如何处理它。如果只是用于节省空间,那么可以忽略它,直接使用结构体。

然而,这通常不是使用联合体的原因。一个常见的原因是提供两种或更多访问相同数据的方式。例如,整数和4个字节的数组的联合体是将32位整数的字节分离出来的一种(众多)方式之一。

另一个原因是当结构体中的数据来自外部源,例如网络数据包时。通常,封装联合体的结构体中的一个元素是告诉您哪个联合体是有效的 ID。

在这两种情况下,都不能盲目地忽略联合体并将其转换为两个(或更多)字段不重合的结构体。


3
我认为史蒂夫的回答中重要的是,你真的需要理解你正在移植的特定联合的用法。C# 中可能有其他更优雅地满足需求的结构。 - AaronLS
3
我理解@AaronLS的意思,但如果有关于在C#中优雅地完成这个常见操作的文档或其他内容的链接会更好。我可以猜想一些基础方法,比如根据使用情况使用属性来返回联合类型中一个值的“转换”,就像它是另一个成员一样。但我想知道在常见情景下是否还有其他优雅的方法。 - 40detectives

6

在C/C++中,联合体用于在同一内存位置上叠加不同的成员。因此,如果您有一个包含int和float的联合体,则它们都使用相同的4个字节的内存来存储,显然写入其中一个会破坏另一个(因为int和float具有不同的位布局)。

在.Net中,Microsoft选择了更安全的选项,并未包括此功能。

编辑:除了互操作性。


可能是因为.NET更高级,您可能不必担心显式序列化数据并在小端和大端机器之间传输。联合提供了一种很好的转换方式,使用前面评论中提到的技巧。 - tloach
2
使用显式布局结构不仅限于Interop。非引用类型的基元和公共成员可以以任意方式重叠其他非引用类型的基元和公共成员,并具有完全定义的行为,无论代码是否将这些结构传递给外部代码。在这方面,.NET支持比“现代”C更低级别的构造。 - supercat

4

如果您正在使用union将一个类型的字节映射到另一个类型,则在C#中可以使用BitConverter

float fubar = 125f; 
int killroy = BitConverter.ToInt32(BitConverter.GetBytes(fubar), 0);

或者;
int killroy = 125;
float fubar = BitConverter.ToSingle(BitConverter.GetBytes(killroy), 0);

0
你可以编写一个简单的包装器,但在大多数情况下,只需使用一个对象即可,这样会更清晰易懂。
    public class MyUnion
    {
        private object _id;
        public T GetValue<T>() => (T)_id;
        public void SetValue<T>(T value) => _id = value;
    }

-1

个人而言,我会完全忽略 UNION 并将 Killroy 和 Fubar 实现为单独的字段

public struct Foo
{
    float bar;
    int Kilroy;
    float Fubar;
}

使用 UNION 可以节省 int 分配的 32 位内存...这在现今的应用程序中并不会成为决定性因素。


9
这可能取决于库中其他部分如何访问它,有些情况下,您可以将数据写入一个实例并从另一个实例读取相同的数据,但格式可能会略有不同,如果将其拆分为两个不同的变量,则该功能将无法正常工作。 - KPexEA
1
@KPexEA 我同意。然而,如果另一方面,这是一种类型的联合,其中某个标志指示哪个字段是适当的,则这将很好地工作。我认为真正重要的是在决定如何移植它之前,了解每种情况下联合的用法和目的。 - AaronLS
5
如果你想创建一个包含10或20个联合类型的结构体,在结构体作为值传递时,传递几个字节比每个参数传递1K要快得多。 - Brain2000

-4
public class Foo
{
    public float bar;
    public int killroy;

    public float fubar
    {
        get{ return (float)killroy;}
        set{ killroy = (int)value;}
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接