niedziela, 15 maja 2016

Struktury

W ostatnim wpisie wprowadzającym do tematyki obiektowości omówione zostały podstawowe zagadnienia dotyczące klas, czyli typów referencyjnych. W tej notatce skupimy się na strukturach, które są typami wartościowymi, podobnie jak typy wbudowane takie jak `int` czy `double`. Struktury mają niemal identyczne możliwości jak klasy, posiadają metody, pola, właściwości oraz konstruktory. Możliwe jest również określenie poziomu dostępności. Pojawiają się jednak pewne różnice.

Zacznijmy od deklaracji struktury, służy do tego słowo kluczowe `struct`. W nawiązaniu do wcześniej przygotowanej klasy Identifier, zastąpienie słowa kluczowego `class` słowem `struct` nie kończy tematu. Struktury mają pewne ograniczenia. Przede wszystkim to kompilator definiuje konstruktor domyślny (bezargumentowy), przez co jego ręczna deklaracja zgłaszana jest jako błąd. Możliwe jest jednak zdefiniowanie konstruktorów sparametryzowanych. CLR w sposób wydajny inicjalizuje strukturę, przez co potrzebny jest konstruktor, którego zadanie ogranicza się do ustawienia wartości domyślnych dla typów wartościowych oraz `null` dla typów referencyjnych. Usunięcie konstruktora domyślnego pozbawia nas możliwości zliczenia instancji.

Kolejna różnica to konieczność zdefiniowania działania operatora `==`. W przypadku typów referencyjnych, czyli klas, możemy posłużyć się statyczną metodą `ReferenceEquals`, porównującą tożsamość obiektów. C# w przypadku struktur nie definiuje działania domyślnego dla operatora `==`, nie mniej nie jest on wymagany do chwili porównania struktur. Jeśli jednak zostanie on zdefiniowany, zostaniemy poinformowani przez kompilator o konieczności określenia zasad działania dla operatora `!=`. Może się wydawać, że operator `!=` zostanie utworzony automatycznie jako przeciwieństwo `==`, tak się jednak nie dzieje. Co więcej, może dojść do sytuacji, gdzie oba operatory zwrócą `false`. Po dodaniu definicji dla operatorów, kompilator zakomunikuje poprzez ostrzeżenie, aby zdefiniować metody `Equals` oraz `GetHashCode`. Wraz z utworzeniem definicji operatorów powinniśmy nadpisać metodę `Equals`, tak aby zapewnić identyczne działanie. W przypadku metody `Equals`, przed wykonaniem porównania, konieczne jest sprawdzenie, czy dostarczony obiekt jest zgodny z typem Identifier, w tym celu korzystamy z operatora `is`. Dopiero mając pewność, wykonujemy rzutowanie poprzez użycie `(Identifier)obj)`, a następnie porównujemy obiekty. Metoda `GetHashCode` służy jako domyślna funkcja skrótu. Więcej o zasadach jej działania przeczytasz tutaj. Na koniec przykład na podstawie tego wszystkiego, co zostało zaprezentowane.

using System;

namespace Struct
{
    class Program
    {
        static void Main(string[] args)
        {
            Identifier i0 = new Identifier(10);
            Console.WriteLine("i0 = {0}", i0.GetNextID());
            Console.WriteLine("i0 = {0}", i0.GetNextID());
            Console.WriteLine("i0 = {0}", i0.GetNextID());

            Identifier j1 = new Identifier();
            Console.WriteLine("j1 = {0}", j1.GetNextID());

            Console.WriteLine("TotalGetID = {0}", Identifier.TotalGetID);

            object a = new object();
            

            Console.WriteLine("i0 == j1 : {0}", i0 == j1);

            Console.ReadLine();
        }
    }

    struct Identifier
    {
        private int currentID;

        public Identifier(int initialValueID)
        {
            currentID = initialValueID++;
            TotalGetID = 0;
        }

        public int GetNextID()
        {
            TotalGetID += 1;
            return currentID += 1;
        }

        public static int TotalGetID
        {
            get; private set;
        }

        public static bool operator == (Identifier i0, Identifier j1)
        {
            return i0.currentID == j1.currentID;
        }

        public static bool operator !=(Identifier i0, Identifier j1)
        {
            return i0.currentID != j1.currentID;
        }

        public override bool Equals(object obj)
        {
            if(obj is Identifier)
            {
                return this == ((Identifier)obj);
            }
            else
            { 
                return false;
            }
        }

        public override int GetHashCode()
        {
            return currentID;
        }
    }
}

Troska Robert