C ++ Union in C #

78

Ich übersetze eine in C ++ geschriebene Bibliothek in C #, und das Schlüsselwort 'union' existiert einmal. In einer Struktur.

Was ist die richtige Art, es in C # zu übersetzen? Und was macht es? Es sieht ungefähr so ​​aus;

struct Foo {
    float bar;

    union {
        int killroy;
        float fubar;
    } as;
}
Viktor Elofsson
quelle
Es mag sicherer sein, aber wenn Sie mit C-Bibliotheken interagieren, die diese Art von Datenstrukturen bereitstellen, wird durch diese Entscheidung in C # die rudimentäre Kapselung Ihrer C / C ++ - Strukturen ausgeglichen. Ich versuche mit so etwas umzugehen: struct LibrarySType {AnotherType * anotherTypeBuff; int oneSetOfFlags; int anotherSetOfFlags; union {struct {int structMember1; ...} oneUseOfThisLibraryType; struct {char * structMember2; ...} anotherUseOfThisLibraryType; ...} u; int64 * moreStuff; ... Sie haben die Idee} Jetzt habe ich diese clevere Datenstruktur nicht erfunden, aber sie ist Teil einer Hersteller-API, die ich brauche
Steve Wart

Antworten:

82

Sie können dafür explizite Feldlayouts verwenden:

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

Ungetestet. Die Idee ist, dass zwei Variablen dieselbe Position in Ihrer Struktur haben. Sie können natürlich nur einen davon verwenden.

Weitere Informationen zu Gewerkschaften finden Sie im Struktur-Tutorial

Armin Ronacher
quelle
7
Beachten Sie, dass dies nur funktioniert, wenn es sich bei den beteiligten Typen um primitive Typen handelt (wie int und float). Sobald Objekte beteiligt sind, werden Sie nicht mehr zugelassen.
Khoth
12
Funktioniert es mit Werttypen (wie Enum) oder nur mit primitiven Typen?
Taylor Leese
1
Wenn Sie andere komplexe Typen haben, kann der zweite eine Eigenschaft sein, die in den / vom anderen konvertiert wird. Hängt von der Verwendung der Union ab und davon, wie sie in anderem Code verwendet wird, den Sie portieren.
AaronLS
1
"Sie können natürlich nur einen von ihnen verwenden." kann unklar sein. Sie können auf beide zugreifen und diese festlegen, sie überschreiben sich jedoch gegenseitig. Ich würde diesen Punkt klarstellen.
Xonatron
@ Khoth Sie können alles in einem Feld in einer Struktur halten. Gleiches gilt für explizite Strukturlayouts.
22

Sie können sich nicht wirklich entscheiden, wie Sie damit umgehen sollen, ohne etwas darüber zu wissen, wie es verwendet wird. Wenn es nur zum Platzsparen verwendet wird, können Sie es ignorieren und einfach eine Struktur verwenden.

Dies ist jedoch normalerweise nicht der Grund, warum Gewerkschaften eingesetzt werden. Es gibt zwei häufige Gründe, sie zu verwenden. Eine besteht darin, zwei oder mehr Möglichkeiten bereitzustellen, um auf dieselben Daten zuzugreifen. Zum Beispiel ist eine Vereinigung eines int und eines Arrays von 4 Bytes eine (von vielen) Möglichkeiten, die Bytes einer 32-Bit-Ganzzahl zu trennen.

Das andere ist, wenn die Daten in der Struktur von einer externen Quelle stammen, beispielsweise einem Netzwerkdatenpaket. Normalerweise ist ein Element der Struktur, die die Union einschließt, eine ID, die angibt, welche Variante der Union wirksam ist.

In keinem dieser Fälle können Sie die Vereinigung blind ignorieren und in eine Struktur konvertieren, in der die zwei (oder mehr) Felder nicht übereinstimmen.

Steve Fallows
quelle
3
Was ich für wichtig in Steves Antwort halte, ist, dass Sie die Verwendung der jeweiligen Union, die Sie portieren, wirklich verstehen müssen. Es kann andere Konstrukte in C # geben, die die Anforderungen auf elegantere Weise erfüllen.
AaronLS
3
Obwohl ich verstehe, was @AaronLS bedeutet, wäre es schön, einen Link zu Dokumenten oder etwas über gängige Methoden zu haben, um dies elegant in C # zu tun . Ich kann einige der sehr grundlegenden Möglichkeiten erraten, abhängig von der Verwendung, wie die Verwendung von Eigenschaften, um eine "Besetzung" eines Werts der Union zurückzugeben, wie es das andere Mitglied war. Aber fragen Sie sich, welche anderen eleganten Möglichkeiten für die gängigen Szenarien gelten könnten.
40 Detectives
4

Wenn Sie die verwenden union, um die Bytes eines der Typen dem anderen zuzuordnen, können Sie BitConverterstattdessen in C # verwenden .

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

oder;

int killroy = 125;
float fubar = BitConverter.ToSingle(BitConverter.GetBytes(killroy), 0);
Steve Lillis
quelle
4

In C / C ++ wird Union verwendet, um verschiedene Mitglieder am selben Speicherort zu überlagern. Wenn Sie also eine Union aus einem Int und einem Float haben, verwenden beide dieselben 4 Byte Speicher zum Speichern, wobei das Schreiben in eines offensichtlich das andere beschädigt (seitdem int und float haben unterschiedliche Bitlayouts).

In .Net entschied sich Microsoft für die sicherere Wahl und schloss diese Funktion nicht ein.

EDIT: außer interop

Nir
quelle
Dies könnte daran liegen, dass .NET auf einer höheren Ebene ausgeführt wird und Sie sich wahrscheinlich nicht um die explizite Serialisierung von Daten und die Übertragung zwischen Little-Endian- und Big-Endian-Computern kümmern müssen. Gewerkschaften bieten eine gute Möglichkeit zur Konvertierung, indem sie den Trick verwenden, den ein vorheriger Kommentar vermerkt hat.
Tloach
1
Die Verwendung von Strukturen mit explizitem Layout ist nicht auf Interop beschränkt. Grundelemente und öffentliche Mitglieder von Nichtreferenztypen können andere Grundelemente und öffentliche Mitglieder von Nichtreferenztypen auf beliebige Weise mit vollständig definiertem Verhalten überlagern, unabhängig davon, ob Code die fraglichen Strukturen jemals an externen Code weitergibt oder nicht. In dieser Hinsicht unterstützt .NET Konstrukte auf niedrigerer Ebene als "modernes" C.
Supercat
1

Persönlich würde ich die UNION alle zusammen ignorieren und Killroy und Fubar als separate Felder implementieren

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

Die Verwendung einer UNION spart 32 Bit Speicher, die vom int zugewiesen werden. Heutzutage wird keine App mehr erstellt oder beschädigt.

ckramer
quelle
8
Dies funktioniert möglicherweise nicht, je nachdem, wie andere Teile der Bibliothek darauf zugreifen. In einigen Fällen können Sie in eine Instanz schreiben und von der anderen lesen, um dieselben Daten zu erhalten. In einem etwas anderen Format wird diese Funktionalität jedoch unterbrochen, wenn Sie dies tun
Teilen Sie
1
@ KPexEA Ich stimme zu. Wenn es sich jedoch um die Art der Vereinigung handelt, bei der ein Flag angibt, welches der beiden Felder das richtige ist, funktioniert dies einwandfrei. Ich denke, was wirklich wichtig ist, ist, die Verwendung und den Zweck der Gewerkschaft in jedem Fall zu verstehen, bevor entschieden wird, wie sie portiert werden soll.
AaronLS
4
Wenn Sie eine Struktur mit 10 oder 20 vereinigten Typen erstellen möchten, da Strukturen byval sind, ist es viel schneller, ein paar Bytes als 1 KB pro Argument zu übergeben.
Brain2000
-2
public class Foo
{
    public float bar;
    public int killroy;

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