Geschrieben von: Christoph Wille
Kategorie: C#
This printed page brought to you by AlphaSierraPapa
Wer aus der C/C++ Welt kommt, kennt die Unions und ihr Funktionsprinzip bereits: diese erlauben es, an einer Speicheradresse unterschiedliche Datentypen zu speichern. Mit erstaunlich geringem Aufwand lassen sich Unions auch in C# nachbilden, und zwar mit Hilfe der Attribute StructLayout und FieldOffset.
Die beiden erwähnten Attribute dienen im Normalfall dafür, daß das Speicherlayout einer gewissen Struktur vordefiniert wird, und nicht von der CLR nach ihrem Gutdünken optimiert wird. Dieses automatische Layout muß besonders bei Struct's verhindert werden, die per PInvoke an Betriebssystemfunktionen übergeben werden - und für diesen Zweck wurden die Attribute eingeführt.
Dadurch, daß man das Speicherlayout genau definieren kann, eröffnet sich aber auch die Möglichkeit, eine Variable über andere "drüberzulegen". Damit das nicht zu theoretisch ist, hier eine Beispiel-Union:
[StructLayout(LayoutKind.Explicit)] public struct SampleUnion { [FieldOffset(0)] public bool Flag1; [FieldOffset(1)] public bool Flag2; [FieldOffset(2)] public bool Flag3; [FieldOffset(3)] public bool Flag4; [FieldOffset(0)] public long Composite; }
Die Struct hat fünf Members, vier davon sind boolsche Werte, der fünfte ist vom Datentyp long. Unter normalen Umständen würde die Struct gesamt 4 mal 1 Byte plus 1 mal 4 Byte also 8 Byte Speicher verbrauchen - nur diese Struct ist anders. Die Änderung kommt durch die Verwendung von FieldOffset ins Spiel: damit kann ich sagen, an welcher Byteposition der jeweilige Member positioniert werden soll (StructLayout garantiert mein Layout). Und was habe ich gemacht? Nun, Composite beginnt am Anfang der Struct, und wird von allen vier boolschen Members überlappt. Somit ist die Struct 4 Byte groß.
Das bedeutet nun in deutschen Worten folgendes: verändere ich einen der boolschen Members, ändert sich der Wert von Composite. Ändere ich hingegen Composite, ändern sich die boolschen Members, die bitmäßig im Bereich der Änderungen liegen (also nicht notwendigerweise immer alle). Ansehen kann man sich das Verhalten mit folgendem Sample:
using System; using System.Runtime.InteropServices; [StructLayout(LayoutKind.Explicit)] public struct SampleUnion { [FieldOffset(0)] public bool Flag1; [FieldOffset(1)] public bool Flag2; [FieldOffset(2)] public bool Flag3; [FieldOffset(3)] public bool Flag4; [FieldOffset(0)] public long Composite; } class Sample { public static void Main() { SampleUnion su = new SampleUnion(); su.Flag1 = false; Console.WriteLine(su.Composite.ToString()); su.Flag2 = true; Console.WriteLine(su.Composite.ToString()); su.Flag3 = false; Console.WriteLine(su.Composite.ToString()); su.Flag4 = true; Console.WriteLine(su.Composite.ToString()); }
Am Anfang ist Composite noch 0, doch der Wert ändert sich, sobald eines der Flags auf true gesetzt wird.
Jetzt haben wir zwar eine Union mit vier boolschen Flags, aber wozu kann man in dieser speziellen Struct den long-Wert einsetzen? Das folgende Beispiel (union.cs) zeigt - versteckt - eine Möglichkeit:
// using und struct-Definition zur leichteren Lesbarkeit entfernt class Sample { public static void Main() { SampleUnion su = new SampleUnion(); su.Flag1 = false; su.Flag2 = true; su.Flag3 = false; su.Flag4 = true; DumpUnion(su); DumpUnion2(su.Composite); } public static void DumpUnion(SampleUnion su) { Console.WriteLine("Flag1 " + su.Flag1.ToString()); Console.WriteLine("Flag2 " + su.Flag2.ToString()); Console.WriteLine("Flag3 " + su.Flag3.ToString()); Console.WriteLine("Flag4 " + su.Flag4.ToString()); Console.WriteLine("Composite " + su.Composite.ToString()); } public static void DumpUnion2(long nUnionValue) { SampleUnion su = new SampleUnion(); su.Composite = nUnionValue; Console.WriteLine("Flag1 " + su.Flag1.ToString()); Console.WriteLine("Flag2 " + su.Flag2.ToString()); Console.WriteLine("Flag3 " + su.Flag3.ToString()); Console.WriteLine("Flag4 " + su.Flag4.ToString()); Console.WriteLine("Composite " + su.Composite.ToString()); } }
Der Verwendungszweck wird in DumpUnion2 ersichtlich - ich kann meine Union als ganz simple long-Variable an Funktionen übergeben, allerdings jederzeit nach Belieben die originale Union wieder erstellen - es sind nur zwei Zeilen:
SampleUnion su = new SampleUnion(); su.Composite = nUnionValue;
Die Möglichkeit eine Union als andere Datentypen zu repräsentieren beginnt dann seine Vorteile zu entfalten, wenn man nur Teile einer Struct übergeben möchte, und nicht gleich alle Daten. Und mit der Möglichkeit andere Datentypen in ein und dieselbe Speicheradresse zu legen eröffnet sich eine ganze Reihe von interessanten Anwendungen. Ein Beispiel sei erwähnt: es ist einfacher und schneller einen long-Wert per SOAP zu marshalen als die Struct gesamt.
Der durchschnittliche Programmierer wird kaum mit Unions in Berührung kommen, es ist aber doch sehr beruhigend wenn man weiß, daß man auch unter .NET's rigiden Regiment zumindest ein wenig mit dem Speicher spielen darf.
This printed page brought to you by AlphaSierraPapa
Klicken Sie hier, um den Download zu starten.
http://www.aspheute.com/code/20020207.zip
Datentypen in C#
http:/www.aspheute.com/artikel/20000726.htm
Enums lesbar machen
http:/www.aspheute.com/artikel/20010215.htm
Sichere Konvertierungen von Referenztypen
http:/www.aspheute.com/artikel/20001019.htm
Variable Parameterlisten in Funktionen
http:/www.aspheute.com/artikel/20020125.htm
Web Services 101 in ASP.NET
http:/www.aspheute.com/artikel/20010621.htm
©2000-2006 AspHeute.com
Alle Rechte vorbehalten. Der Inhalt dieser Seiten ist urheberrechtlich geschützt.
Eine Übernahme von Texten (auch nur auszugsweise) oder Graphiken bedarf unserer schriftlichen Zustimmung.