Unie w C#–zastosowanie

W ostatnim poście przedstawiłem sposób na implementację unii w C#. Dziś kilka przykładów. Proszę jednak zwrócić uwagę, że unie zostały wprowadzone w czystym C, który nie miał nic wspólnego z programowaniem obiektowym. Dzisiaj możemy wiele konstrukcji lepiej napisać, wykorzystując klasy. Unie również ważną rolę odgrywały w optymalizacji.  W c# sprawa wygląda inaczej, ponieważ jeśli zgodziliśmy się na zarządzaną pamięć przez GC, raczej nie zależy nam na oszczędzaniu kilku bajtów.

Niemniej jednak klasyczny przykład wykorzystania unii to struktury VARIANT. Struktura taka posiada jedno pole identyfikujące oraz zagnieżdżaną unię. Identyfikator opisuje, jak struktura powinna się zachować i jakie dane opisuje:

public struct Variant
{
   public int Type;
   public Union Union;       
}
[StructLayout(LayoutKind.Explicit)]
public struct Union
{
   [FieldOffset(0)]
   public char CharValue;
   [FieldOffset(0)]
   public short ShortValue;
   [FieldOffset(0)]
   public float FloatValue;
}

Zamiast int Type można byłoby użyć ENUM – zdecydowanie bardziej nadaję się do tego przykładu. Nie chciałem jednak zaciemniać kodu. Unia posiada kilka pól o  różnym typie danych. Następnie przejrzyjmy się funkcji, która wyświetla taką strukturę:

static private void Display(Variant variant)
{
   switch (variant.Type)
   {
       case 0:
           Console.WriteLine(variant.Union.CharValue);
           break;
       case 1:
           Console.WriteLine(variant.Union.ShortValue);
           break;
       case 2:
           Console.WriteLine(variant.Union.FloatValue);
           break;
   }
}

Unia przechowuje dane w tej samej pamięci. Zatem za pomocą unii, łatwo uzyskać dane w różnym formacie. W praktyce nie ograniczamy oczywiście się do  short czy float ale do różnych buforów (byte[], void*) itp.  Poniższy kod wyświetli dane w różnym formacie w zależności od Type:

Variant variant=new Variant();
variant.Union.CharValue = 'A';

variant.Type = 0;
Display(variant);

variant.Type = 1;
Display(variant);

variant.Type = 2;
Display(variant);

Proszę zwrócić szczególnie uwagę na wynik Type==2 czyli liczby zmiennoprzecinkowej. Powyższa struktura VARIANT szczególnie jest przydatna do opisywania kolorów. W końcu ten sam kolor można wyrazić za pomocą np. tablicy 3 bajtów (RGB) oraz liczby całkowitej składającej się z tych pól. Za pomocą unii nie trzeba dokonywać konwersji pomiędzy byte[] a int – unia umożliwia dostęp do tych samych danych za pomocą różnych accessor’ów.

Unie również były namiastką polimorfizmu ale tego nie będę opisywał na blogu .NET bo w świecie C# są oczywiście do tego dużo bardziej wyrafinowane mechanizmy. Powyższy przykład też można byłoby lepiej napisać wykorzystując programowanie obiektowe…. Dla mnie jednak zaletą unii jest możliwość zapisu i odczytu tej samej komórki pamięci za pomocą różnych interfejsów dostępowych. Zwykłe jest to bardzo przydatne w programowaniu low-level. Na przykład (źródło wikibooks):

union item {
  short theItem;  
  struct { char lo; char hi; } portions;
};

Fragment kodu jest w CPP ale powinien być zrozumiały. W łatwy sposób możemy modyfikować mniej i bardziej istotne bajty, bez dokonywania operacji bitowych (shifting itp.).

Leave a Reply

Your email address will not be published.