Klasy i struktury

Różnice między klasą a strukturą w C#, szczególnie na początku przygody z programowaniem, mogą wydawać się niewielkie i nieistotne, jednak pytania o nie często pojawiają się na rozmowach kwalifikacyjnych dla junior developerów, a ich znajomość jest dodatkowo kluczowa ze względów wydajnościowych,

Typ referencyjny i wartościowy

Jeżeli programowałeś w C++ pamiętasz zapewne, że różnica między klasą a strukturą polega tam jedynie na tym, że class ma pola domyślnie prywatne, a struct publiczne, natomiast w pamięci reprezentowane są w taki sam sposób. W C# jest inaczej. Klasa jest typem referencyjnym, alokowana jest zatem na stercie (heap), zaś struktura jest typem wartościowym i żyje na stosie (stack). Temat ten dokładniej poruszę w następnym wpisie, na ten moment najważniejsze jest to, że typy wartościowe w momencie przypisania są w całości kopiowane

int x = 11;
int y = x; // tworzymy nową komórkę w pamięci
x = 12;
Console.WriteLine(x); // 12
Console.WriteLine(y); // 11

natomiast w wypadku typów referencyjnych kopiowany jest adres komórki pamięci.

class MyClass
{
    public string Text {get; set; }
}
var first = new MyClass();
first.Text = "Not changed";
var second = first; // referencja do tego samego obszaru pamięci
second.Text = "Changed";
Console.WriteLine(first.Text); // Changed
Console.WriteLine(second.Text); // Changed

Dziedziczenie

Struktury w C# nie wspierając mechanizmu dziedziczenia, są domyślnie zablokowane (sealed), mogą jednak implementować interfejsy.

Konstruktory

W przypadku klas możemy tworzyć dowolną liczbę konstruktorów, należy jedynie pamiętać, że stworzenie pierwszego własnego konstruktora uniemożliwia nam wykorzystanie konstruktora bezparametrycznego dla danej klasy. Jeżeli chcemy go używać musimy stworzyć go samodzielnie.

W przypadku struktur istnieją pewne ograniczenia w tworzeniu konstruktorów. Po pierwsze, nie można stworzyć własnego konstruktora bez parametrów. Kompilator tworzy dla nas domyślny konstruktor, który inicjalizuje wszystkie pola domyślnymi wartościami w sposób bardzo wydajny. Dodatkowym ograniczeniem jest fakt, iż w konstruktorze z parametrami wszystkie pola struktury muszą być zainicjalizowane. Rozważmy przykłady:

struct Vector
{
    public int X;
    public int Y;
    public int Z;

    public Vector(int x, int y) // ŹLE
    {
        X = x;
        Y = y;
    }

    public Vector(int x, int y) // DOBRZE
    {
        X = x;
        Y = y;
        Z = 0;
    }

    public Vector(int x, int y, int z) // DOBRZE
    {
        X = x;
        Y = y;
        Z = z;
    }

    public Vector(int x, int y) : this() // DOBRZE
    {
        X = x;
        Y = y;
    }
}

Pierwszy przypadek spowoduje pojawienie się błędu na etapie kompilacji programu. Zauważmy jednak, że możemy wywołać konstruktor domyślny this(), co spowoduje, że wszystkie niezainicjalizowane przez nas pola otrzymają wartość domyślną.

Wartości domyślne

Sposób inicjalizacji struktur uniemożliwia definiowanie domyślnych wartości dla pól.

struct MyStruct
{
    public int Value = 1; // ŹLE
}

Możliwe jest jednak definiowanie stałych i pól statycznych.

struct MyStruct
{
    public const int X = 1; // DOBRZE
    public static int Y = 1; // DOBRZE
}

W przypadku klas nie ma ograniczeń.

class MyClass
{
    public int Value = 1; // DOBRZE
}

Słowo kluczowe new

new używamy, aby utworzyć nową instancję klasy. Operator ten jest dostępny również dla struktur, jednak jego działanie jest inne. Powoduje jedynie wywołanie konstruktora i inicjalizację pól struktury, a jego użycie nie jest konieczne. Pola można inicjalizować ręcznie.

Vector vector;
vector.X = 1;
vector.Z = 3;

Należy pamiętać, aby w takim wypadku zainicjalizować wszystkie pola, z których korzystamy.

Console.WriteLine(vector.Y);

Powyższy kod spowoduje błąd kompilacji, gdyż vector.Y nie zostało zainicjalizowane.

Nullable

Dla klas możemy posiadać pustą referencję.

MyClass myClass = null;

Aby uzyskać podobne zachowanie w przypadku struktur (jest to potrzebne np. przy tworzeniu modeli bazodanowych) należy skorzystać z Nullable.

MyStruct? myStruct = null;
Nullable<MyStruct> myStruct = null;
Advertisements