So verwenden Sie C#-Structs zum Erstellen eines Union-Typs (ähnlich wie C-Unions)

So verwenden Sie C#-Structs zum Erstellen eines Union-Typs (ähnlich wie C-Unions)

# Unions im C-Stil in C#

Union-Typen werden in mehreren Sprachen, wie der C-Sprache, verwendet, um mehrere verschiedene Typen zu enthalten, die sich "überschneiden" können. Mit anderen Worten, sie können unterschiedliche Felder enthalten, die alle mit demselben Speicheroffset beginnen, selbst wenn sie unterschiedliche Längen und Typen haben. Dies hat den Vorteil, dass sowohl Speicherplatz gespart als auch eine automatische Konvertierung durchgeführt wird. Stellen Sie sich als Beispiel eine IP-Adresse vor. Intern wird eine IP-Adresse als Ganzzahl dargestellt, aber manchmal möchten wir auf die andere Byte-Komponente zugreifen, wie in Byte1.Byte2.Byte3.Byte4. Dies funktioniert für alle Werttypen, seien es primitive wie Int32 oder long, oder für andere Strukturen, die Sie selbst definieren.

Wir können den gleichen Effekt in C# erzielen, indem wir explizite Layoutstrukturen verwenden.

using System;
using System.Runtime.InteropServices;

// The struct needs to be annotated as "Explicit Layout"
[StructLayout(LayoutKind.Explicit)]
struct IpAddress
{
    // The "FieldOffset" means that this Integer starts, an offset in bytes.
    // sizeof(int) 4, sizeof(byte) = 1
    [FieldOffset(0)] public int Address;
    [FieldOffset(0)] public byte Byte1;
    [FieldOffset(1)] public byte Byte2;
    [FieldOffset(2)] public byte Byte3;
    [FieldOffset(3)] public byte Byte4;

    public IpAddress(int address) : this()
    {
        // When we init the Int, the Bytes will change too.
        Address = address;
    }

    // Now we can use the explicit layout to access the 
    // bytes separately, without doing any conversion.
    public override string ToString() => $"{Byte1}.{Byte2}.{Byte3}.{Byte4}";
}

Nachdem wir Struct auf diese Weise definiert haben, können wir es verwenden, als würden wir eine Union in C verwenden. Erstellen wir beispielsweise eine IP-Adresse als zufällige Ganzzahl und ändern dann das erste Token in der Adresse auf „100“, indem wir es von „A.B.C.D ' bis '100.B.C.D':

var ip = new IpAddress(new Random().Next());
Console.WriteLine($"{ip} = {ip.Address}");
ip.Byte1 = 100;
Console.WriteLine($"{ip} = {ip.Address}");

Ausgabe:

75.49.5.32 = 537211211
100.49.5.32 = 537211236

Demo ansehen

# Union Types in C# können auch Struct-Felder enthalten

Abgesehen von Primitiven können die expliziten Layout-Strukturen (Unions) in C# auch andere Strukturen enthalten. Solange ein Feld ein Werttyp und keine Referenz ist, kann es in einer Union enthalten sein:

using System;
using System.Runtime.InteropServices;

// The struct needs to be annotated as "Explicit Layout"
[StructLayout(LayoutKind.Explicit)]
struct IpAddress
{
    // Same definition of IpAddress, from the example above
}

// Now let's see if we can fit a whole URL into a long

// Let's define a short enum to hold protocols
enum Protocol : short { Http, Https, Ftp, Sftp, Tcp }

// The Service struct will hold the Address, the Port and the Protocol
[StructLayout(LayoutKind.Explicit)]
struct Service
{
    [FieldOffset(0)] public IpAddress Address;
    [FieldOffset(4)] public ushort Port;
    [FieldOffset(6)] public Protocol AppProtocol;
    [FieldOffset(0)] public long Payload;

    public Service(IpAddress address, ushort port, Protocol protocol)
    {
        Payload = 0;
        Address = address;
        Port  = port;
        AppProtocol = protocol;
    }

    public Service(long payload)
    {
        Address = new IpAddress(0);
        Port = 80;
        AppProtocol = Protocol.Http;
        Payload = payload;
    }

    public Service Copy() => new Service(Payload);

    public override string ToString() => $"{AppProtocol}//{Address}:{Port}/";
}

Wir können nun verifizieren, dass die gesamte Service Union in die Größe eines Long (8 Byte) passt.

var ip = new IpAddress(new Random().Next());
Console.WriteLine($"Size: {Marshal.SizeOf(ip)} bytes. Value: {ip.Address} = {ip}.");

var s1 = new Service(ip, 8080, Protocol.Https);
var s2 = new Service(s1.Payload);
s2.Address.Byte1 = 100;
s2.AppProtocol = Protocol.Ftp;

Console.WriteLine($"Size: {Marshal.SizeOf(s1)} bytes. Value: {s1.Address} = {s1}.");
Console.WriteLine($"Size: {Marshal.SizeOf(s2)} bytes. Value: {s2.Address} = {s2}.");

Demo ansehen

# Bemerkungen

Union-Typen werden in mehreren Sprachen verwendet, insbesondere in der C-Sprache, um mehrere verschiedene Typen zu enthalten, die sich im selben Speicherplatz "überschneiden" können. Mit anderen Worten, sie können unterschiedliche Felder enthalten, die alle mit demselben Speicheroffset beginnen, selbst wenn sie unterschiedliche Längen und Typen haben. Dies hat den Vorteil, dass sowohl Speicherplatz gespart als auch automatisch konvertiert wird.

Bitte beachten Sie die Kommentare im Konstruktor des Struct. Die Reihenfolge, in der die Felder initialisiert werden, ist äußerst wichtig. Sie möchten zuerst alle anderen Felder initialisieren und dann den Wert, den Sie ändern möchten, als letzte Anweisung festlegen. Da sich die Felder überschneiden, zählt der zuletzt eingestellte Wert.


No