Własny typ Variant

Jeden z dwóch artykułów poświęconych zmiennym wariantowym i samoalokującym strukturom danych, tj. takim, które dostosowują swój rozmiar do użytych indeksów. Artykuł prezentuje rozwiązanie w C# i tworzy parę z artykułem poświęconym rozwiązaniu w Delphi. Link do artykułu z kodem Delphi znajduje się na końcu.

***

Spis treści

1 Spis treści
2 Część I: Tworzenie własnego typu wariantowego
     2.1 Pojęcie zmiennej wariantowej
     2.2 Rekord wariantowy
     2.3 Rekord wariantowy jako unia
     2.4 Pola w klasie Variant
     2.5 Konstruktory zmiennej
     2.6 Własności prywatne do konwersji typów
     2.7 Operatory podstawienia
     2.8 Przykładowa realizacja operatora dodawania "+"
     2.9 Przykłady użycia
3 Część II: samoalokująca tablica dynamiczna
     3.10 Samoalokująca tablica dynamiczna
     3.11 Efektywność rozwiązania i jej zwiększenie. Pomiary czasu
     3.12 Program do Części II
4 Literatura
5 Projekt do pobrania

Część I: Tworzenie własnego typu wariantowego

Pojęcie zmiennej wariantowej

Zmienna wariantowa, to taka, dla której typ przechowywanej wartości może być różny, jednak zawarty w ściśle określonym zbiorze typów, co oznacza, że typ wartości nie jest zupełnie dowolnym typem.

Zmienna wariantowa oprócz przechowywanej wartości zawiera też pole, nazywane polem znacznikowym, które określa bieżący typ wartości.

Bieżący typ wartości, jest ustawiany w momencie podstawienia wartości do zmiennej lub zainicjowania zmiennej przy pomocy konstruktora .

Do zmiennej wariantowej można podstawiać wartości jednego typu, a odczytywać je, jako wartości innego typu. Umożliwiają to operacje konwersji między typami, będące składową zmiennej wariantowej.

Rekord wariantowy

Prezentowany w artykule rekord wariantowy został zaimplementowany jako klasa z trzema typami wartości, tj.
a) typem całkowitym (pole int i),
b) typem logicznym (pole bool b)
c) oraz typem tekstowym (pole string s).

Polem typu jest pole VariantType t. Jest zdefiniowane przez typ wyliczeniowy, niemniej w dalszej treści będzie mowa o typie wartości przechowywanej w zmiennej wariantowej.

Przy tworzeniu rekordów wariantowych w C# zalecane jest utworzenie osobnego pola wartości dla każdego z występujących typów w rozumieniu odrębnych obszarów pamięci.

/*
 * Rekord wariantowy
 */ 
// przestrzeń nazw "własny typ Variant"
namespace CustomVariant
{
  // typ wyliczeniowy do określania typu danej Variant 
  enum VariantType
  {
    isInt,  // typ całkowity
    isBool, // typ logiczny
    isStr   // typ tekstowy
  }

  // klasa do przechowywania wartości i typu danej Variant
  class VariantRecord
  {
    public VariantType t; // typ wartości
    public int i;         // wartość całkowita
    public bool b;        // wartość logiczna   
    public string s;      // wartość tekstowa
  }
}

Rekord wariantowy jako unia

W ogólnym przypadku możliwe jest częściowe lub całkowite wykorzystanie tego samego obszaru pamięci przez pola wartości.

Mówi się wtedy o strukturze unii. Można taką strukturę utworzyć w C#. Jest podana poniżej, jednak tego typu struktura stwarza różne problemy i nie jest zalecana.

/*
 * Rekord wariantowy jako unia
 */
using System.Runtime.InteropServices;

// przestrzeń nazw "własny typ Variant"
namespace CustomVariant
{
  [StructLayout(LayoutKind.Explicit)]
  public struct VariantUnion
  {
    [FieldOffset(0)]
    VariantType t;
    [FieldOffset(4)] // 4 = sizeof(VariantType)
    public int i;    // pola i, b, s znajdują się
    [FieldOffset(4)] // w tym samym obszarze pamięci
    public bool b;
    [FieldOffset(4)]
    public string s;
  }
}

Pola w klasie Variant

Po zdefiniowaniu struktury przechowującej typ i wartość można przystąpić do tworzenia klasy.

W klasie Variant dodano pole isAssigned. Jest ono wykorzystywane dla uniknięcia pojawiania się wyjątku, gdy zmienna nie była jeszcze podstawiona, a już nastąpił jej odczyt.

Wówczas nie wystąpi wartość null, a dla typu int będzie 0, dla typu bool będzie False i dla typu string będzie pusty łańcuch tekstowy.

/*
 * Pola w klasie Variant
 */
using System;

// przestrzeń nazw "własny typ Variant"
namespace CustomVariant
{
  // klasa Variant
  public partial class Variant
  {
    public bool isAssigned; // flaga mówiąca, że dana była przynajmniej
                            // raz podstawiona wartością

    VariantRecord r;        // rekord przechowujący wartość i typ danej
  }
}

Konstruktory zmiennej

Konstruktorów używa się w momencie tworzenia obiektu typu Variant.

Na szczególną uwagę zasługuje konstruktor bezparametrowy i pole isAssigned, mówiące o stanie przypisania wartości do zmiennej.

Jeżeli wartość nie jest przypisana (tj. nie było podstawienia), flaga isAssigned sprawia, że nie zostanie zwrócona wartość null, a domyślny rezultat np. dla typu int będzie zero.

Innymi słowy zmienna Variant "widziana spoza" klasy zawsze ma wartość nie powodującą błędu.

Przykładowo wartości danych z tablicy można odczytywać bezpośrednio po utworzeniu tablicy:

Variant[, ] arr = new Variant[100, 200]; // deklaracja
//…
int x = arr[10, 100]; // x = 0, jeżeli wcześniej nie podstawiono wartości.

W typowej sytuacji konstruktory podstawiają przekazany im parametr do pola wartości i ustawiają pole znacznikowe typu.

/*
 * Konstruktory zmiennej
 */ 
// przestrzeń nazw "własny typ Variant"
namespace CustomVariant
{
  // klasa Variant
  public partial class Variant
  {
    // konstruktor bezparametrowy
    public Variant()
    {
      if (r == null) // jednorazowe tworzenie rekordu z daną Variant
      {
        r = new VariantRecord();
      }
      // dana nie była jeszcze podstawiana wartością
      isAssigned = false;
    }

    // konstruktor dla danych całkowitych
    public Variant(int x)
    {
      if (r == null) // jednorazowe tworzenie rekordu z daną Variant
      {
        r = new VariantRecord();
      }
      r.t = VariantType.isInt; // ustawienie typu isInt
      r.i = x;                 // podstawienie wartości do pola i 
      isAssigned = true;       // dana została podstawiona wartością
    }

    // konstruktor dla danych logicznych
    public Variant(bool x)
    {
      if (r == null) // jednorazowe tworzenie rekordu z daną Variant
      {
        r = new VariantRecord();
      }
      r.t = VariantType.isBool; // ustawienie typu isBool
      r.b = x;                  // podstawienie wartości do pola b
      isAssigned = true;        // dana została podstawiona wartością   
    }

    // konstruktor dla danych tekstowych
    public Variant(string x)
    {
      if (r == null) // jednorazowe tworzenie rekordu z daną Variant
      {
        r = new VariantRecord();
      }
      r.t = VariantType.isStr;  // ustawienie typu isStr
      r.s = x;                  // podstawienie wartości do pola s
      isAssigned = true;        // dana została podstawiona wartością  
    }
  }
}

Własności prywatne do konwersji typów

Poniższe własności służą do odczytu wartości zmiennej wariantowej.

W zależności od tego jakiego typu wartość jest odczytywana: AsInt – wartość typu całkowitego, AsBool – wartość logiczna, AsStr – wartość tekstowa,
o ile jest to konieczne, dokonywana jest konwersja z bieżącego typu wartości przechowywanej przez zmienną wariantową na typ odczytywany.

Poniższe własności służą tylko do odczytu. Właśnie w momencie odczytu wartości dokonuje się konwersja.
Tylko wtedy równocześnie znany jest typ odczytywany i typ przechowywany wartości.

Możliwość odczytu wartości jednego typu, jako wartość innego typu to cecha charakterystyczna zmiennych wariantowych.

Wzajemne przekształcenie typów int i bool, nie sprawia problemu – wartość (int) równa zero daje False, a wszystkie wartości int różne od zera dają True.
Bool na int daje 1 dla wartości True i zero dla wartości False.
Również nie ma problemu z przekształceniem dowolnego typu w typ string, gdzie otrzymuje się tekstowy odpowiednik wartości,
gdzie wartości int przekształcone do string należałoby zapisywać w cudzysłowie np. "24" – tak jest w kodzie programu. W zwykłym tekście zapis bez cudzysłowu może być mylący.

Zamiana string na bool oraz na int jest poprawna tylko, gdy powiedzie się operacja TryParse, co oznacza, że tekst reprezentował słowny zapis wartość bool lub int, tj. był "False", "True" lub był ciągiem cyfr, a tak nie musi być i wtedy jest wywoływany wyjątek błędnego rzutowania typu.

/*
 * Własności prywatne do konwersji typów
 */
using System;

// przestrzeń nazw "własny typ Variant"
namespace CustomVariant
{
  // klasa Variant
  public partial class Variant
  {
    // prywatna własność tylko do odczytu jako wartość całkowita
    private int AsInt
    {
      get
      {
        if (isAssigned)                   // jeżeli wartość jest podstawiona:
        {
          if (r.t == VariantType.isInt)   // jeżeli jest int: 
          {                               // zwróć pole i
            return r.i;
          }
          if (r.t == VariantType.isBool)  // jeżeli jest bool: 
          {
            if (r.b == false)             // jeżeli b jest false
            {                             // zwróć zero
              return 0;
            }
            else                          // jeżeli b jest true
            {                             // zwróć jeden
              return 1;
            }
          }
          else                            // jeżeli jest string:
          {
            int v;

            if (int.TryParse(r.s, out v)) // jeżeli s reprezentuje
            {                             // odpowiednik tekstowy wartości int
              return v;                   // zwróć rezultat operacji int.Parse
            }
            else // jeżeli s nie reprezentuje odpowiednika tekstowgo wartości int
            {    // wywołaj wyjątek
              throw new Exception("Błąd rzutowania typu string na int");
            }
          }
        }
        else
        {
          return 0;                       // jeżeli wartość nie jest podstawiona:
        }                                 // zwróć zero
      }
    }

    // prywatna własność tylko do odczytu jako wartość logiczna
    private bool AsBool
    {
      get
      {
        if (isAssigned)                       // jeżeli wartość jest podstawiona:
        {
          if (r.t == VariantType.isInt)       // jeżeli jest int:
          {
            if (r.i == 0)                     // jeżeli i jest zero
            {                                 // zwróć false
              return false;
            }
            else                              // jeżeli i jest różne od zera
            {                                 // zwróć true
              return true;
            }
          }
          else if (r.t == VariantType.isBool) // jeżeli jest bool:
          {                                   // zwróć pole b
            return r.b;
          }
          else                                // jeżeli jest string:
          {
            bool v;

            if (bool.TryParse(r.s, out v))    // jeżeli s reprezentuje
            {                                 // odpowiednik tekstowy wartości bool
              return v;                       // zwróć rezultat operacji bool.Parse
            }
            else // jeżeli s nie reprezentuje odpowiednika tekstowgo wartości bool
            {    // wywołaj wyjątek
              throw new Exception("Błąd rzutowania typu string na bool");
            }
          }
        }
        else                                  // jeżeli wartość nie jest podstawiona:
        {                                     // zwróć false
          return false;
        }
      }
    }

    // prywatna własność tylko do odczytu jako wartość tekstowa
    private string AsStr
    {
      get
      {
        if (isAssigned)                       // jeżeli wartość jest podstawiona:
        {
          if (r.t == VariantType.isInt)       // jeżeli jest int:
          {                                   // zwróć i w postaci string
            return r.i.ToString();            // np. -10, 0, 23 jako string
          }
          else if (r.t == VariantType.isBool) // jeżeli jest bool:
          {                                   // zwróć b w postaci string
            return r.b.ToString();            // tylko False lub True
          }
          else                                // jeżeli jest string:
          {                                   // zwróć s
            return r.s;
          }
        }
        else                                  // jeżeli wartość nie jest podstawiona:
        {                                     // zwróć pusty string
          return "";
        }
      }
    }
  }
}

Operatory podstawienia

Operatory podstawienia umożliwiają zarówno podstawianie wartości należącej do jednego z typów int, bool, string do zmiennej typu Variant, jak i podstawianie zmiennej typu Variant do zmiennej należącej do jednego z ww. typów.

Innymi słowy, operatory te zapewniają dwukierunkowość podstawień.

W szczególności odczyt wartości zmiennej Variant w przypadku, gdy jest ona null, powoduje utworzenie obiektu klasy Variant przed pobraniem wartości.

Dzięki temu unika się występowania wyjątku dla zmiennych nie przypisanych wcześniej wartością, co omówiono w punkcie dotyczącym konstruktorów.

Wartości zwracane przez nowy obiekt Variant można w tym przypadku nazwać domyślnymi.

/*
 * Operatory podstawienia
 */ 
// przestrzeń nazw "własny typ Variant"
namespace CustomVariant
{
  // klasa Variant
  public partial class Variant
  {
    // operator podstawienia liczby całkowitej
    // pod zmienną typu Variant
    public static implicit operator Variant(int x)
    {
      return new Variant(x);
    }

    // operator podstawienia wartości logicznej
    // pod zmienną typu Variant
    public static implicit operator Variant(bool x)
    {
      return new Variant(x);
    }

    // operator podstawienia tekstu
    // pod zmienną typu Variant
    public static implicit operator Variant(string x)
    {
      return new Variant(x);
    }

    // operator podstawienia zmiennej typu Variant
    // pod zmienną typu int
    public static implicit operator int(Variant x)
    {
      if (x == null)       // jeżeli zmienna x jest null
      {                    // zainicjuj ją konstruktorem
        x = new Variant(); // bezparametrowym
      }
      return x.AsInt;
    }

    // operator podstawienia zmiennej typu Variant
    // pod zmienną typu bool
    public static implicit operator bool(Variant x)
    {
      if (x == null)       // jeżeli zmienna x jest null
      {                    // zainicjuj ją konstruktorem  
        x = new Variant(); // bezparametrowym
      }
      return x.AsBool;
    }

    // operator podstawienia zmiennej typu Variant
    // pod zmienną typu string
    public static implicit operator string(Variant x)
    {
      if (x == null)       // jeżeli zmienna x jest null
      {                    // zainicjuj ją konstruktorem
        x = new Variant(); // bezparametrowym
      }
      return x.AsStr;
    }
  }
}

Przykładowa realizacja operatora dodawania "+"

Dla potrzeb artykułu wybrano jeden operator, który można zastosować do każdego z typów (int, bool, string).

Jest to operator "+", który:
a) dla wartości typu int oznacza dodawanie arytmetyczne
b) dla wartości typu bool oznacza sumę logiczną
c) dla wartości typu string oznacza konkatenację (połączenie) łańcuchów tekstowych

Przyjęto rozwiązanie, w którym pierwsza z dodawanych wartości decyduje o typie, na jaki rzutowane są pozostałe składniki dodawnia.

Uwzględniono zarówno dodawanie do zmiennych Variant, jak i dodawanie do wartości nie należących do typu Variant np.:

// operator dodawania do wartości int
public static Variant operator +(int x1, Variant x2)
{
  return (new Variant(x1)) + x2;
}

Poniżej pełny kod realizacji operatora "+".

/*
 * Przykładowy operator - suma arytmetyczna, logiczna i złączenie tekstów
 */
// przestrzeń nazw "własny typ Variant"
namespace CustomVariant
{
  // klasa Variant
  public partial class Variant
  {
    // operator dodawania
    public static Variant operator +(Variant x1, Variant x2)
    {
      // Jeżeli pierwszy parametr jest int sumuj arytmetycznie
      // x1 narzuca typ pozostałych składników, tu: int
      if (x1.r.t == VariantType.isInt)
      {
        return new Variant(x1.AsInt + x2.AsInt);
      }
      // Jeżeli pierwszy parametr jest bool sumuj logicznie
      // x1 narzuca typ pozostałych składników, tu: bool
      else if (x1.r.t == VariantType.isBool)
      {
        return new Variant(x1.AsBool || x2.AsBool);
      }
      // Jeżeli pierwszy parametr jest string złącz teksty
      // x1 narzuca typ pozostałych składników, tu: string
      else
      {
        return new Variant(x1.AsStr + x2.AsStr);
      }
    }

    // operator dodawania do wartości int
    public static Variant operator +(int x1, Variant x2)
    {
      return (new Variant(x1)) + x2;
    }

    // operator dodawania do wartości bool
    public static Variant operator +(bool x1, Variant x2)
    {
      return (new Variant(x1)) + x2;
    }

    // operator dodawania do wartości string
    public static Variant operator +(string x1, Variant x2)
    {
      return (new Variant(x1)) + x2;
    }
  }
}

Przykłady użycia

Poniższy kod pokazuje na różnych przykładach sposób użycia przedstawionego własnego typu wariantowego.

Nie wykonano testów innych, niż te w obrębie typów int, bool, string. Użycie operatora "+" np. dla typów double oraz int nie jest znane.

/*
 * Przykłady użycia utworzonego typu Variant
 */
using System;
using CustomVariant; // włączenie przestrzeni nazw "własny typ Variant"

// przestrzeń nazw aplikacji
namespace CustomVariantApp
{
  class Program
  {
    static void Main()
    {
      // przykłady użycia zmiennych typu Variant
      // w komentarzach podano wyświetlane na konsoli teksty

      Variant v1 = "Ala i ";
      Variant v2 = "kot";
      string s = v1 + v2;
      Console.WriteLine(s);  // Ala i kot

      v2 = 13;
      Variant v3 = true;
      int i = v2 + v3;
      Console.WriteLine(i);  // 14

      s = v1 + v2;
      Console.WriteLine(s);  // Ala i 13

      v1 = 1;
      v2 = "7";
      i = v1 + v2;
      Console.WriteLine(i);  // 8

      v2 = "pies As";
      try
      {
        s = v1 + v2;
      }
      catch (Exception e)    // Błąd rzutowania typu string na int
      {
        Console.WriteLine(e.Message); 
      }

      Variant v4 = false;
      Variant v5 = 1;
      bool b = v4 + v5 + "False";
      Console.WriteLine(b);  // True;

      Variant x2 = 1;
      i = 1 + x2;
      Console.WriteLine(i);  // 2

      x2 = " i pies";
      s = "kot" + x2;
      Console.WriteLine(s);  // kot i pies

      x2 = true;
      b = false + x2;
      Console.WriteLine(b);  // True

      Console.WriteLine("\nNaciśnij dowolny klawisz.");
      Console.ReadKey();
    }
  }
}

Część II: samoalokująca tablica dynamiczna

Samoalokująca tablica dynamiczna

Istotą tablicy samoalokującej jest to, że jej rozmiaru nie trzeba deklarować. Tablica sama dostosowuje swój rozmiar do maksymalnego użytego indeksu. Możliwe jest zainicjowanie tablicy określonym rozmiarem, a także funkcją, zwaną generatorem, daną deklaratywnie w postaci wzoru na wartość kolejnego elementu.

Przeznaczeniem tablicy są zbiory danych o charakterze rzadkim, co oznacza, że większość ich elementów jest zero, a w kontekście opisywanego tu typu Variant może być także w większości wartościami False lub pustymi tekstami. Struktura ta może znaleźć zastosowanie np. w tzw. tablicach rozproszonych, których analogią jest klasa Hashtable lub obecnie preferowana Dictionary.

Nie jest powiedziane, że nie można używać tablicy samoalokującej w przypadku ilości elementów porównywalnych z jej rozmiarem. Wymaga to jednak, o ile chodzi o iteracje po kolejnych indeksach, wypełniania od elementów o najwyższym indeksie do elementów o najniższym indeksie. Tym samym uzyskuje się jedną alokację na początku pętli, a w przypadku elementów o niższych od maksymalnego indeksach miejsce na nie w pamięci jest już zaalokowane.

Omówienie samoalokującej tablicy w C# nawiązuje do artykułu poświęconego podobnemu zagadnieniu w języku Delphi (link podano na końcu w punkcie Literatura).

/*
 * Samoalokująca tablica dynamiczna
 */
using System;

// przestrzeń nazw "własny typ Variant"
namespace CustomVariant
{
  // klasa: wariantowa tablica dynamiczna
  // uwaga: o ile w konstruktorze inicjowanie wartości elementów odbywa się
  // po wcześniejszym ustawieniu rozmiaru i jest tylko jedna alokacja, o tyle
  // na dalszym etapie może być tak, że alokacje będą spowalniać działanie tablicy
  // toteż jej zastosowanie sprawdzi się lepiej przy tablicach rzadkich,
  // gdzie większość elementów to zera (ewentualnie: false lub pusty tekst)
  class VariantDynArray
  {
    private Variant[] a; // dane typu Variant w tablicy

    // konstruktor tablicy, size: początkowy rozmiar tablicy, 
    // f: generator inicjujący elementy
    public VariantDynArray(int size, Func<int, Variant> f)
    {
      a = new Variant[size];

      for (int i = 0; i < size; i++)
      {
        this[i] = f(i);
      }
    }

    // własność tablicowa do zapisu i odczytu elementów o indeksie index
    public Variant this[int index]
    {
      set                               // zapis
      {
        if (index < 0)
        {
          throw new Exception("Indeks tablicy poza zakresem.");
        }
        else if (a == null)             // jeżeli tablica jest null, utwórz obiekt tablicy
        {                               // o początkowym rozmiarze index + 1
          try                           // uwaga: indeksy zaczynają się od zera
          {
            a = new Variant[index + 1];
          }
          catch
          {
            throw new Exception("Błąd alokacji tablicy.");
          }
        }
        else if (index > a.Length - 1)  // jeżeli indeks jest większy od rozmiaru tablicy,
        {                               // zwiększ rozmiar tablicy do wymaganego przez
                                        // indeks
          try
          {
            Array.Resize(ref a, index + 1);
          }
          catch
          {
            throw new Exception("Błąd alokacji tablicy.");
          }
        }

        a[index] = value;               // podstawienie wartości
      }

      get // odczyt
      {
        if (index < 0)
        {
          throw new Exception("Indeks tablicy poza zakresem.");
        }
        else if (a == null)             // jeżeli tablica jest null, utwórz obiekt tablicy
        {                               // o początkowym rozmiarze index + 1
          try                           // uwaga: indeksy zaczynają się od zera
          {
            a = new Variant[index + 1];
          }
          catch
          {
            throw new Exception("Błąd alokacji tablicy.");
          }
        }

        if (index > a.Length - 1)       // jeżeli indeks jest większy od rozmiaru tablicy,
        {                               // zwiększ rozmiar tablicy do wymaganego przez
          try                           // indeks
          {
            Array.Resize(ref a, index + 1);
          }
          catch
          {
            throw new Exception("Błąd alokacji tablicy.");
          }
        }

        return a[index];                // zwrócenie wartości
      }
    }
  }
}

Efektywność rozwiązania i jej zwiększenie. Pomiary czasu

W punkcie tym przedstawiono porównanie czasów wypełniania tablicy w zależności od użytego sposobu iteracji. Wypada to korzystnie dla iteracji "downto", i niekorzystnie dla iteracji od indeksów najmniejszych do największych. Na każdym komputerze wypadnie inaczej. Pomiar dla tysiąca elementów na użytym komputerze był:
Szybciej 166 ticks
Wolniej 3361 ticks
Jest to różnica o rząd wielkości.

/*
 * Efektywność rozwiązania dla tablicy samoalokującej
 */
using CustomVariant; // włączenie przestrzeni nazw "własny typ Variant"
using System;
using System.Diagnostics;

namespace CustomVariantApp
{
  // szybka i wolna metoda interacji przy dodawaniu 
  // nowych elementów do tablicy dynamicznej
  class Comparison
  {
    // wartości brzegowe indeksów
    private const int minIndex = 0;
    private const int maxIndex = 1000;

    // generator (inicjuje pola na zero)
    private static Variant f(int x)
    {
      return 0; // f1(x) = 0 lub f(index) = 0
    }

    // szybka iteracja - alokacja elementu o maksymalnym indeksie
    // na początku
    public static void FastLoopAssign()
    {
      VariantDynArray array = new VariantDynArray(0, f);

      Stopwatch t = new Stopwatch();
      t.Start();
      for (int i = maxIndex; i >= minIndex; i--)
      {
        /*
         * Array.Resize(ref array, maxIndex + 1) // jedna alokacja
         */
        array[i] = 50;
      }
      t.Stop();
      Console.WriteLine("\nSzybciej {0} ticks", t.ElapsedTicks);
    }

    // wolna alokacja - alokacja dla każdego elementu
    public static void SlowLoopAssign()
    {
      VariantDynArray array = new VariantDynArray(0, f);
      Stopwatch t = new Stopwatch();
      t.Start();
      for (int i = minIndex; i <= maxIndex; i++)
      {
        /*
         * Array.Resize(ref array, i + 1) // 100 alokacji
         */
        array[i] = 50;
      }
      t.Stop();
      Console.WriteLine("Wolniej {0} ticks", t.ElapsedTicks);
    }
  }
}

Program do Części II

Program pokazuje użycie konstruktora z generatorem inicjującym i to, że nie ma potrzeby deklarowania rozmiaru tablicy tu: w przypadku wartości indeksu 7.

/*
 * Przykład użycia przedstawianej klasy
 */
using CustomVariant; // włączenie przestrzeni nazw "własny typ Variant"
using System;

namespace CustomVariantApp  // przestrzeń nazw aplikacji
{
  class Program
  {
    // funkcja-generator wykorzystywana przez konstruktor
    private static Variant f(int x)
    {
      return x + 1; // f(x) = x + 1 lub f(index) = index + 1
    }

    private static void Main()
    {
      VariantDynArray array = new VariantDynArray(5, f);

      array[7] = 12;                        // 5 pierwszych wartości zainicjował 
                                            // konstruktor -generator
                                            // zera to wartości domyślne dla danych 
                                            // z flagą isAssigned = False (niepodstawionych)
                                            // wartość na pozycji 7 (licząc od zera) to 12
      for (int j = 0; j <= 7; j++)
      {                                     // wartości wyświetlane na konsoli
        Console.Write((int)array[j] + " "); // 1 2 3 4 5 0 0 12
      }

      Comparison.FastLoopAssign();
      Comparison.SlowLoopAssign();

      Console.WriteLine("\nNaciśnij dowolny klawisz.");
      Console.ReadKey();
    }
  }
}

Literatura

Artur Protasewicz, madmike, msm, Samoalokująca tablica. Tablice dynamiczne. / Self-allocating array. Dynamic arrays., 4programmers.net
Romuald Jagielski, Tablice rozproszone, WNT, ISBN 83-204-0247-6

Projekt do pobrania

Kliknięcie linku poniżej umożliwia pobranie projektu w wersji MS Visual Studio Community 2017.
W przypadku Części II kod klasy CustomVariant umieszczono w jednym module.

Pobierz projekt do Części I
Pobierz projekt do Części II

0 komentarzy