C# Language
Come utilizzare C # Structs per creare un tipo Union (simile a C Unions)
Ricerca…
Osservazioni
I tipi dell'Unione sono usati in diverse lingue, in particolare il linguaggio C, per contenere diversi tipi che possono "sovrapporsi" nello stesso spazio di memoria. In altre parole, potrebbero contenere diversi campi che iniziano tutti con lo stesso offset di memoria, anche quando potrebbero avere lunghezze e tipi diversi. Ciò ha il vantaggio sia di risparmiare memoria, sia di eseguire la conversione automatica.
Si prega di notare i commenti nel costruttore della Struct. L'ordine in cui i campi sono inizializzati è estremamente importante. Si desidera innanzitutto inizializzare tutti gli altri campi e quindi impostare il valore che si intende modificare come ultima istruzione. Poiché i campi si sovrappongono, l'ultima impostazione del valore è quella che conta.
Unioni stile C in C #
I tipi di unione sono usati in diverse lingue, come il linguaggio C, per contenere diversi tipi che possono "sovrapporsi". In altre parole, potrebbero contenere diversi campi che iniziano tutti con lo stesso offset di memoria, anche quando potrebbero avere lunghezze e tipi diversi. Ciò ha il vantaggio sia di risparmiare memoria, sia di eseguire la conversione automatica. Pensa ad un indirizzo IP, ad esempio. Internamente, un indirizzo IP viene rappresentato come un numero intero, ma a volte si desidera accedere al diverso componente Byte, come in Byte1.Byte2.Byte3.Byte4. Questo funziona per qualsiasi tipo di valore, sia esso primitivo come Int32 o lungo, o per altre strutture definite dall'utente.
Possiamo ottenere lo stesso effetto in C # usando le strutture di layout esplicito.
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}";
}
Avendo definito Struct in questo modo, possiamo usarlo come useremmo un'unione in C. Ad esempio, creiamo un indirizzo IP come numero intero casuale e quindi modifichiamo il primo token dell'indirizzo su "100", cambiandolo da 'ABCD' a '100.BCD':
var ip = new IpAddress(new Random().Next());
Console.WriteLine($"{ip} = {ip.Address}");
ip.Byte1 = 100;
Console.WriteLine($"{ip} = {ip.Address}");
Produzione:
75.49.5.32 = 537211211
100.49.5.32 = 537211236
I tipi di unione in C # possono anche contenere campi Struct
Oltre alle primitive, le strutture di Layout esplicito (Unions) in C #, possono contenere anche altre strutture. Finché un campo è un tipo di valore e non un riferimento, può essere contenuto in un'unione:
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}/";
}
Ora possiamo verificare che l'intera Service Union si adatta alle dimensioni di un lungo (8 byte).
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}.");